Compare commits

...

572 Commits

Author SHA1 Message Date
043d6aa9c3 extend traces on subscribe 2020-05-07 12:23:32 -07:00
ecfab8f7f3 add subscribe 2020-05-07 12:00:39 -07:00
05c38c37aa working historical 2020-05-07 11:37:01 -07:00
ce78925119 wip: added telemetry provider 2020-05-07 10:45:15 -07:00
26aca0f433 working on viewProvider 2020-05-06 11:31:17 -07:00
41259bbd40 added hardcoded test plot 2020-05-05 16:00:48 -07:00
580640ff47 basic plotly plugin structure 2020-05-05 12:50:55 -07:00
a4aec5d492 Merge branch 'master' of https://github.com/nasa/openmct 2020-05-01 10:46:57 -07:00
23303c910e Don't allow recursive Preview actions #2775 (#2869)
* Don't allow recursive Preview actions #2775

* actionsToBeIncluded and actionsToBeSkipped passed in as options object.

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

This reverts commit f501d0b4ba.

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

This reverts commit 5563cbea3a.

* Don't allow recursive Preview actions

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

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

* renamed test:coverage and test:debug.

* circle-ci to use test:coverage.

* reduced test coverage thresholds.

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

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

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

* removed stopclock call.

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-30 10:47:49 -07:00
cfadb9f4fd Fixes #2901 (#2902)
- Mod PreviewAction to prevent folders from being previewed;

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

* do not send telemetryObjects to telemetry api request cal

* normalize data on requests

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-29 14:56:07 -07:00
cb5d47f66f Use only the values required for description (#2919)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-28 16:58:58 -07:00
ea90d02d66 Show the Styles tab for non creatable layout objects including condition sets (#2975) 2020-04-28 13:10:29 -07:00
95f73d8eb8 [Conditionals] fix #2961 in master (#2969)
* use correct id for telemetry requests

* request and subscription data cache should be mutually exclusive

use latest timestamp for any/all requests

* do not add prop to datum

remove unnecessary if check

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-28 13:02:23 -07:00
0dff431f4a Merge branch 'master' of https://github.com/nasa/openmct 2020-04-27 12:02:12 -07:00
a37c686993 Merge pull request #2968 from nasa/revert-2885-lodash-upgrade-test
Revert "Lodash upgrade"
2020-04-24 11:55:44 -07:00
f12166097c Revert "Lodash upgrade (#2885)"
This reverts commit d103a22fa0.
2020-04-24 11:53:31 -07:00
61d238a097 Merge branch 'master' of https://github.com/nasa/openmct 2020-04-23 16:30:01 -07:00
d103a22fa0 Lodash upgrade (#2885)
* upgraded lodash, changed method names
* native implementations as requested
2020-04-23 10:38:44 -07:00
04a60cfcbb fixes #2713 (#2928)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-22 15:22:11 -07:00
8d723960f4 removed acorn from package.json (#2906)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-21 15:50:45 -07:00
6d3cd2c699 Upgrade angular from 1.4.14 to 1.7.9 (#2955)
* successfully upgraded to v1.6 with $compileProvider.preAssignBindingsEnabled(true)
* removed $compileProvider.preAssignBindingsEnabled(true), wrapped constructors for plot and chart inside onInit function
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-21 15:34:12 -07:00
87bf94fe0a Updated year in index.html (#2930) 2020-04-21 15:28:10 -07:00
af93823b6f Elasticsearch support for change to typeless API (#2941)
* added elasticsearch to bundlemap
2020-04-21 15:23:43 -07:00
f9deb80350 Merge branch 'master' of https://github.com/nasa/openmct 2020-04-16 15:01:42 -07:00
4a39ddf425 Check for any and all criteria (#2948) 2020-04-16 15:01:14 -07:00
83c273b976 Remove telemetry from criteria when not editing a condition set (#2933) 2020-04-16 12:32:32 -07:00
021d730814 resolve merge conflicts 2020-04-13 09:05:43 -07:00
7dd81beb03 Remove telemetry data cache if a telemetry endpoint is removed (#2916) 2020-04-10 16:49:29 -07:00
1842d3923c [Conditionals] Only provide telemetry for incoming telemetry that is used (#2914)
* Ensures that results for a specific datapoint are evaluated atomically.

* Removes timestamp based evalutation from conditionManager

* Remove unused code

* remove generating timestamp for telemetry data

* get results directly instead of using events

* remove unused listeners, events, and helpers

* linting

* remove commented code

* telemetry criterion stores its own result

* refactor all/any telemetry criterion to use new evaluator

* tie in requests and eliminate unused code

* use current timesystem to compare latest

* scope function names

* AllTelemetryCriterion extends TelemetryCriterion

* fix telemetrycriterion and unit testing

* fix unit tests

* check if telemetry is used at condition manager level

* move check to condition manager

* remove whitespace

Co-authored-by: Joshi <simplyrender@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-10 16:45:09 -07:00
26838635b6 Ensures correct results are returned for conditions and criteria for a given telemetry datapoint (#2904)
* Ensures that results for a specific datapoint are evaluated atomically.
* Remove generating timestamp for telemetry data
* Get results directly instead of using events
* Refactor all/any telemetry criterion to use new evaluator
* Use current timesystem to compare latest
* AllTelemetryCriterion extends TelemetryCriterion

Co-authored-by: David Tsay <david.e.tsay@nasa.gov>
Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-10 15:57:38 -07:00
ae62b15abf Merge branch 'fix-default-output' of github.com:nasa/openmct into fix-default-output 2020-04-10 15:44:42 -07:00
ba41c1a30e fix unit tests 2020-04-10 15:43:48 -07:00
b9a85d9c4d Merge branch 'master' into fix-default-output 2020-04-10 15:34:10 -07:00
11f2c35bb2 [Notebook]: Entries filter #2820 (#2864)
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-10 15:33:50 -07:00
80eab8bad1 fix telemetrycriterion and unit testing 2020-04-10 15:26:18 -07:00
b2d8d640ae Merge branch 'master' into fix-default-output 2020-04-10 15:23:10 -07:00
766f48c1ba Handles static and mixed styles for multiple items in a layout (#2907)
* Show non specific styles when updating multiple item styles
* Save sub object styles to it's domain object
* Layout UI tweak
* Fixes flexible layout bug.
* Fixes font size bug in telemetry view
* Fixes issues with newly places TVOs including transparent properties.
* Fixes #2908
* Say NO to 'transparent' === '__no_value'
- Fixes #2895;
* Ensure styles are correctly applied to domain objects and drawing objects when selected individually
* Ensure none treatment is correctly applied to objects when multple selecting
* Fix intial box border
* Tweaks to c-text-view layout
- Vertically center text;
- Normalize padding;
- Overflow: hidden;

* Tweaks to Clock and Timer layout
- Fixes #2893;
- Vertically center text;
- Normalize padding;
- Overflow: hidden;
- `position: absolute` when in Layout;

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-10 15:22:47 -07:00
da7b93f9b3 Notebook context menu (#2888)
Notebook popup menu fix
Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-10 15:17:01 -07:00
56e6fa66c2 Merge branch 'master' into fix-default-output 2020-04-10 15:13:36 -07:00
99c095a69f Fixed condition-improve-reorder branch (#2912)
* wip: changing to condition as drag target

* wip

* wip

* wip

* fixed dragging issues

* fixed dragging classes and added temp border on condition with dragging class

* Conditionals sanding and shimming

- CSS and `all-dragging`;

* wip

* fixed drag end issue and changed dragging class to go on parent condition h

* drag with counter

* wip

* wip

* wip

* return to logic in ConditionCollection.vue

* wip

* completed js part with highlighted c-condition-h on dragover

* restored grippy as draggable elem, improved isValidTarget

* fixed drag text bug

* added moveIndex prop in Condition.vue

* Conditionals drag reorder styling

- Moved `.is-drag-target` class up to conditions-h element;
- Renamed `.all-dragging` to `is-active-dragging`;
- Styling for `__drop-target` elements;

* fixed incorrect default for moveIndex in condition collection, unnecessary reset in condition

* fixed downward move reorder

* removed prevent from dragenter and drag leave, changed @blur to @change for name and output fields

* removed console log

* Repair merge-damaged conditionals.scss

- Manual merge from latest master;

* Test data layout tweaked

- Prevent c-cs__test-data__controls from collapsing;

Co-authored-by: Joel McKinnon <joel.g.mckinnon@nasa.gov>
Co-authored-by: Joel McKinnon <JoelMcKinnon@users.noreply.github.com>
2020-04-10 15:04:04 -07:00
f885e83505 Merge pull request #2911 from nasa/revert-2818-condition-improve-reorder
Revert "Condition improve reorder"
2020-04-10 14:35:41 -07:00
928bc4c68a Revert "Condition improve reorder (#2818)"
This reverts commit 46fedc1a30.
2020-04-10 14:32:48 -07:00
d5539c7ae4 Merge pull request #2898 from nasa/fix-enum-comparison
Fixes enum comparisons
2020-04-10 14:03:05 -07:00
c86a104fb6 Merge branch 'master' into fix-enum-comparison 2020-04-10 13:54:04 -07:00
9fa4707c82 Merge branch 'master' into fix-default-output 2020-04-10 13:04:14 -07:00
7e2cfa36de AllTelemetryCriterion extends TelemetryCriterion 2020-04-10 12:17:55 -07:00
aaa60a1545 scope function names 2020-04-10 11:28:13 -07:00
717231fed2 use current timesystem to compare latest 2020-04-10 11:20:51 -07:00
7fb2bc9729 tie in requests and eliminate unused code 2020-04-10 10:42:31 -07:00
46fedc1a30 Condition improve reorder (#2818)
* wip: changing to condition as drag target

* wip

* wip

* wip

* fixed dragging issues

* fixed dragging classes and added temp border on condition with dragging class

* Conditionals sanding and shimming

- CSS and `all-dragging`;

* wip

* fixed drag end issue and changed dragging class to go on parent condition h

* drag with counter

* wip

* wip

* wip

* return to logic in ConditionCollection.vue

* wip

* completed js part with highlighted c-condition-h on dragover

* restored grippy as draggable elem, improved isValidTarget

* fixed drag text bug

* added moveIndex prop in Condition.vue

* Conditionals drag reorder styling

- Moved `.is-drag-target` class up to conditions-h element;
- Renamed `.all-dragging` to `is-active-dragging`;
- Styling for `__drop-target` elements;

* fixed incorrect default for moveIndex in condition collection, unnecessary reset in condition

* fixed downward move reorder

* removed prevent from dragenter and drag leave, changed @blur to @change for name and output fields

* removed console log

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-04-10 10:02:33 -07:00
addeb635e9 refactor all/any telemetry criterion to use new evaluator 2020-04-10 09:48:31 -07:00
608d63a7b0 telemetry criterion stores its own result 2020-04-10 09:00:39 -07:00
10679e5f4f remove commented code 2020-04-10 00:12:55 -07:00
38b8f03b1a linting 2020-04-09 21:03:56 -07:00
779a42c28c remove unused listeners, events, and helpers 2020-04-09 18:14:19 -07:00
80c2504768 get results directly instead of using events 2020-04-09 17:54:47 -07:00
80359e3f16 remove generating timestamp for telemetry data 2020-04-09 16:20:16 -07:00
3b6ef9b44b Refactor duplicate code into functions 2020-04-09 15:33:52 -07:00
66aa4f099f Remove unused code 2020-04-09 15:22:35 -07:00
aa6c6cb88b Removes timestamp based evalutation from conditionManager 2020-04-09 15:21:25 -07:00
4e5cc840d7 Ensures that results for a specific datapoint are evaluated atomically. 2020-04-09 12:10:29 -07:00
c68edd9b7d Fixes enum comparisons
Adds check for undefined
2020-04-09 09:14:31 -07:00
11574b7c40 Merge pull request #2861 from nasa/fix-telemetryview-styles
Styles for telemetry are stored on their container domain objects
2020-04-08 15:19:15 -07:00
abc2cd2413 Merge branch 'master' into fix-telemetryview-styles 2020-04-08 14:40:54 -07:00
5d74882646 Merge pull request #2871 from nasa/dave/conditionals-own-results
[Conditionals] Request and subscription results are mutually exclusive
2020-04-08 14:21:31 -07:00
9fe7f230e6 Merge branch 'master' into dave/conditionals-own-results 2020-04-08 14:16:50 -07:00
de4c5b3729 Disables conditional and static styles for hyperlinks and summary widgets. (#2887) 2020-04-08 12:30:59 -07:00
2a7901914a Merge branch 'master' into fix-telemetryview-styles 2020-04-08 11:57:33 -07:00
73b0fc6f79 Removes preventNone as per conversation with UX designer. 2020-04-08 11:46:12 -07:00
ddef16795c Conditionals and Notebook UI fixes (#2868)
- Significant fixes for Safari-compatible Flex layout in Condition Set
view;
- Changed visual approach to current-value section;
- Firefox scrollbar coloring
- Fix layout issues in Firefox;
- Consolidate Conditionals styles into single scss file;
- Fix test datum elements layout, better wrapping;
- Better approach to presence/absence of URL property in Condition
Widget;
- Fixes #2853;
- Fix errors in URL property handling in Condition Widget;
- Fixes #2853;
- Fixes #2867 - hide the View Switcher when an object is being edited;
- Refined titling on View Switcher and Notebook menu button;
- Cleaned up styles in l-browse-bar and moved into
ui/layout/layout.scss;
- Removed styles/_layout.scss;
- Hide the main view Edit button when in mobile
2020-04-08 09:36:23 -07:00
d188b9a056 do not mutate function args for criteria results either 2020-04-07 13:22:03 -07:00
f510f3edd0 Removes missed code 2020-04-07 11:59:24 -07:00
e05b0bb562 Address review comments:
Fixes telemetry view visibility and styling issue
Removes none option for border and background styles for drawing objects
2020-04-07 11:34:48 -07:00
713c5e9fb7 Merge branch 'master' into dave/conditionals-own-results 2020-04-06 18:56:08 -07:00
17bca04560 do not mutate function args 2020-04-06 18:53:48 -07:00
61bdadc33c Fixes #2878 (#2879)
- Remove background from `elementStatusColors` mixin;
- Change Indicators to use more correct style of `.s-status-on`;
2020-04-06 15:36:52 -07:00
e0c5bca47d fix unit tests 2020-04-06 14:25:55 -07:00
cdc7c1af64 Removes unused data 2020-04-06 14:04:11 -07:00
3158baa998 Merge branch 'fix-telemetryview-styles' of https://github.com/nasa/openmct into fix-telemetryview-styles 2020-04-06 14:02:45 -07:00
698508fde4 Use the subobject view type to determine where styles should be saved 2020-04-06 14:02:06 -07:00
68a96989e1 Merge branch 'master' into fix-telemetryview-styles 2020-04-06 13:57:41 -07:00
46a6a43234 Merge branch 'master' of https://github.com/nasa/openmct into fix-telemetryview-styles 2020-04-06 13:56:30 -07:00
d41fc27b55 subscriptions should use latest timestamp 2020-04-06 11:17:59 -07:00
24bb96cc90 WIP detach request data from subscription data
extract getLatestTimestamp function
2020-04-03 18:39:26 -07:00
483ee173d6 pass correct args into off listener 2020-04-02 16:34:40 -07:00
469e93d916 Merge pull request #2865 from nasa/criterion-delete
Criterion delete
2020-04-02 10:51:12 -07:00
f96dfcc942 Reassign ids for criteria when we clone conditions 2020-04-02 10:45:37 -07:00
063a6c0e51 Merge branch 'criterion-delete' of https://github.com/nasa/openmct into criterion-delete 2020-04-02 10:40:57 -07:00
7c289d76b6 Revert object.assign change 2020-04-02 10:40:39 -07:00
c617a440eb Merge branch 'master' of https://github.com/nasa/openmct into criterion-delete 2020-04-02 10:40:32 -07:00
8f81a45b9b Removes coverage branch 2020-04-02 10:30:23 -07:00
666459be87 Merge branch 'master' of https://github.com/nasa/openmct into fix-telemetryview-styles 2020-04-02 10:30:07 -07:00
53df89aa5d Merge branch 'master' into criterion-delete 2020-04-02 10:26:57 -07:00
8f553f6327 Merge pull request #2848 from nasa/fix-any-telemetry
Fix LAD requests for any/all telemetry
2020-04-02 10:23:26 -07:00
f91a64483b Adds missing id during create condition 2020-04-02 10:18:41 -07:00
de8d63c09d Merge branch 'master' of https://github.com/nasa/openmct into criterion-delete 2020-04-02 10:11:42 -07:00
58b4a6ebf5 Merge branch 'master' into fix-any-telemetry 2020-04-02 10:06:37 -07:00
9d45080526 Merge pull request #2858 from nasa/dave/conditionals-request-default
request LAD for default condition
2020-04-02 10:05:42 -07:00
d3fe2a6811 Merge branch 'master' of https://github.com/nasa/openmct into fix-telemetryview-styles 2020-04-01 15:52:55 -07:00
97b37edce4 Store telemetry styles on their container domain objects. 2020-04-01 15:51:40 -07:00
d3443518d6 reverted changes unrelated to id 2020-04-01 15:08:31 -07:00
c33314a4bf added uuid to addCriteria, cloneCriteria, changed key of v-for to criteria.id 2020-04-01 14:56:32 -07:00
59eb034ab4 Merge branch 'master' into dave/conditionals-request-default 2020-04-01 14:42:21 -07:00
67d53fb62b [Notebook]: When notebook is not default new entry button does not add new entry #2841 (#2855)
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-01 14:28:52 -07:00
0fd637c0e9 [Notebook]: Error in nextTick: "TypeError: Cannot read property 'focus' of null" #2845 (#2857) 2020-04-01 14:22:11 -07:00
169cc6617a added uuid() in conditionManager 2020-04-01 13:58:58 -07:00
a946325e95 request LAD for default condition 2020-04-01 13:58:27 -07:00
1beba78111 removed call to checkTelemetry in computed prop, removed if in persist(), changed key in v-for for criteria 2020-04-01 13:34:17 -07:00
2edfeaa606 added checkTelemetry call in inputCount computed prop 2020-04-01 12:15:02 -07:00
2a1f9fd063 removed recursive call 2020-04-01 11:10:10 -07:00
63fe92f8ea Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into criterion-delete 2020-04-01 10:37:01 -07:00
35f303ffa4 changed inputCount to computed property 2020-04-01 10:35:49 -07:00
dd70bb470f Merge branch 'master' of https://github.com/nasa/openmct into fix-telemetryview-styles 2020-04-01 10:18:30 -07:00
250fee125a Merge pull request #2854 from nasa/notebook-preview-action-fix
[Notebook] Embed preview action fix
2020-04-01 10:13:01 -07:00
956029c786 Merge branch 'fix-any-telemetry' of https://github.com/nasa/openmct into fix-any-telemetry 2020-04-01 10:11:30 -07:00
b9ab599c35 Fix description and errors for all/any telemetry 2020-04-01 10:10:31 -07:00
5a690932e9 Merge branch 'master' of https://github.com/nasa/openmct into notebook-preview-action-fix 2020-04-01 09:59:57 -07:00
b00757150e use objectPathJSON 2020-04-01 09:58:30 -07:00
8515a411bd Merge branch 'master' of https://github.com/nasa/openmct into fix-any-telemetry 2020-04-01 09:55:55 -07:00
3034ec016a Merge branch 'master' into fix-any-telemetry 2020-04-01 09:55:03 -07:00
a81af1ce34 check format first (#2851)
default to number
2020-03-31 21:04:52 -07:00
64c5725687 pass correct object path, and remove notebook snapshot button from preview 2020-03-31 20:59:20 -07:00
72a3248123 Fix LAD requests for any/all telemetry 2020-03-31 16:48:59 -07:00
ee4a81bdfd Conditionals feature (#2830)
Introduces conditional styling feature.
2020-03-31 15:56:06 -07:00
b90eb80584 Addresses review comments: Rename file, change copyright year, remove commented out code 2020-03-31 15:50:30 -07:00
ebaf702c59 Topic conditionals master merge (#2833)
- Restore updated symbols font file for Condition Widget icon glyph;
- Change "All telemetry | Any telemetry" to all lowercase;
- Fix cursor styles for Condition Widget;
- Fix Safari layout problems with Condition Set/section styles;
2020-03-31 15:36:50 -07:00
3956cd1c06 Merge pull request #2829 from nasa/topic-conditionals-master-merge
Topic conditionals master merge
2020-03-31 14:25:16 -07:00
064cf6747e Adds copyright text 2020-03-31 14:21:30 -07:00
8512b634c7 Merge branch 'master' of https://github.com/nasa/openmct into topic-conditionals 2020-03-31 12:54:12 -07:00
4220a8a68a [Conditionals] default condition handling (#2827)
* Sisplay default condition output by... default
2020-03-31 12:32:06 -07:00
e7e5116773 [Notebook] V2.0 development #2666 (#2755)
* Notebook v2.0
Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-03-31 12:11:11 -07:00
079201273e Merge branch 'master' into topic-conditionals 2020-03-31 12:02:37 -07:00
e4c9f156a7 Merge pull request #2800 from nasa/any-all-telemetry
Any all telemetry option for conditions.
2020-03-31 11:49:23 -07:00
42eeeea374 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into any-all-telemetry 2020-03-31 11:00:00 -07:00
7b060509f5 c-object-label changes brought over from topic-conditionals (#2823)
- SCSS mods;
- Markup in select objects
2020-03-30 17:30:53 -07:00
3ca9e9ae56 When view params change, get a new copy of the domainObject (#2813)
Update browseObject on object change
2020-03-30 17:27:10 -07:00
984bede43b Removed image properties button from display layouts toolbar (#2809)
Removed image properties button from display layouts toolbar
2020-03-30 16:58:46 -07:00
87d838c690 [Tables] Use parsed data to sort (#2785)
* Use parsed values when sorting
2020-03-30 16:57:20 -07:00
072bf361de Store styles for telemetry on domain objects 2020-03-30 16:20:22 -07:00
4e39d9fb84 Test data for condition sets (#2807)
* Test Data
* Persist testData and apply it to the conditionSet
* Do not persist the applied flag, but persist test data
Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-03-30 15:45:07 -07:00
14eaf4e899 Addresses comments: Fixes dropdown logic 2020-03-30 12:17:49 -07:00
a31d10e708 Adds NOT and XOR triggers for conditions (#2816)
* Adds XOR and NOT triggers for conditions
* Adds unit tests and fixes linting issues
2020-03-30 12:09:50 -07:00
f9e88321a3 Condition Widget refinements (#2814)
* New Condition Widget
* Add new glyphs and symbols font files
2020-03-30 11:59:27 -07:00
9e12203b86 Merge branch 'master' into topic-conditionals 2020-03-30 11:19:45 -07:00
a5326a7b95 Merge pull request #2810 from nasa/toolbar-fixes-32720
[Toolbar] fix errors when navigated away from a layout that uses toolbar
2020-03-30 11:13:17 -07:00
84874f22e6 Cast number inputs to a Number so that operations evaluate correctly (#2799)
* Cast number inputs to a Number so that operations evaluate correctly
2020-03-30 10:37:55 -07:00
d00e8b68a5 Merge pull request #2802 from nasa/clone-condition-issues
Clone condition, criteria issues
2020-03-30 09:40:00 -07:00
2907d6d79c moved opy to cloneCondition 2020-03-30 07:13:15 -07:00
a5a4bb87c5 Removed extra if statement, copied only config 2020-03-30 07:06:03 -07:00
389589d7f7 Merge pull request #2806 from nasa/preserve-user-input
Preserve user input when changing criterion comparison
2020-03-29 15:59:31 -07:00
c02cbd1ba7 Merge pull request #2804 from nasa/criterion-type-change
Criterion field change causing incorrect input type
2020-03-29 15:46:18 -07:00
90b475e17b addressed review comments 2020-03-29 14:03:49 -07:00
89a298f5b3 Removes name string fix 2020-03-27 15:25:24 -07:00
f990a14a3c Remove duplicate metadata options 2020-03-27 15:22:50 -07:00
6b00af6ece fix errors when navigated away from a layout that uses toolbar 2020-03-27 10:15:09 -07:00
5ec8ac95c1 Fixes linting issues 2020-03-27 09:42:18 -07:00
5f061001d6 fixed updateOperations, clearDependentFields, removed unnecessary ref 2020-03-27 08:52:23 -07:00
ef3c4ccf47 clear dependent fields when field selector is changed 2020-03-27 08:09:04 -07:00
1a86204637 used lodash cloneDeep for criteria, conditions 2020-03-27 07:31:41 -07:00
0a1959df38 Fixes linting issues 2020-03-26 23:00:59 -07:00
03a690a158 Fixes import 2020-03-26 22:56:39 -07:00
459a055455 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into any-all-telemetry 2020-03-26 22:55:44 -07:00
502d29dd25 Add all and any options for telemetry 2020-03-26 16:44:44 -07:00
bf947a8835 reverted to espresso (#2797) 2020-03-26 15:12:43 -07:00
f0fd0a9cc7 Use parsed telemetry data values for condition evaluations (#2783)
* Use parsed telemetry data values for condition evaluations

* Addresses comments - handles undefined value and implements format and validate formatter methods

* Merge topic-conditionals
2020-03-26 15:11:29 -07:00
7282792da1 fixed refs and ev bugs 2020-03-26 14:57:55 -07:00
ec0291c54d Implement any telemetry and all telemetry options 2020-03-26 14:01:02 -07:00
4413c29abb Merge pull request #2793 from nasa/new-widget
Add Condition Widget
2020-03-26 13:26:47 -07:00
91e1a144ed Merge branch 'master' of https://github.com/nasa/openmct into topic-conditionals 2020-03-26 13:22:59 -07:00
53440c31d5 Handles remove telemetry from condition set - this resets criterion properties (#2791)
Handles remove condition from a condition set that's being used in a conditional style.
Fixes a small bug with stye rule manager request lad results
2020-03-26 13:12:22 -07:00
5128af2531 New Condition Widget, WIP
- Remove temp test styling;
2020-03-26 13:08:44 -07:00
eedc0f13bc adds formatting for strings (#2792) 2020-03-26 13:08:28 -07:00
4172fdf1d5 Merge pull request #2788 from nasa/criterion-enum-persist
Criterion enum persist
2020-03-26 12:58:25 -07:00
7c200df4c4 resolved merge conflict 2020-03-26 12:39:50 -07:00
d1b28e079a Merge branch 'topic-conditionals' into new-widget 2020-03-26 12:34:56 -07:00
23ec838643 Merge pull request #2764 from nasa/condition-persist
Telemetry stickiness issue
2020-03-26 12:34:37 -07:00
271c619c63 Merge pull request #2789 from nasa/fix-description
Fixes description for enums
2020-03-26 12:23:40 -07:00
0552769670 change back to refs 2020-03-26 11:30:00 -07:00
99a9f5b3ba Merge pull request #2790 from nasa/telemetry-metadata-manager-fix-32620
[Metadata] Fails when trying to apply reasonable defaults to an undefined values array
2020-03-26 10:41:29 -07:00
4b535ade31 metadataManager should check for a values array
before calling map on it
2020-03-26 10:25:04 -07:00
de8f8088e2 Merge pull request #2786 from nasa/issue-2773
[Conditions] cleanup subscriptions in style rule manager
2020-03-26 09:53:16 -07:00
32c16416d3 Fixes description for enums 2020-03-26 09:45:44 -07:00
0cf27c349b Merge pull request #2768 from nasa/conditionset-multi-value-operations
Implements two new operations - is one of and is not one of
2020-03-26 09:43:08 -07:00
015aa8c637 Checks for input before processing it. 2020-03-26 09:37:52 -07:00
4a07ddbefc added persist call to change enum input 2020-03-26 09:03:32 -07:00
a81009541c Merge branch 'condition-persist' of https://github.com/nasa/openmct into criterion-enum-persist 2020-03-26 08:22:59 -07:00
1b680cfaca moved persist call into change event method and removed check on $ref 2020-03-26 08:10:35 -07:00
311ff003c0 Merge pull request #2784 from nasa/fix-conditionals
Fixes error while getting metadata when telemetry is not yet available for a criterion
2020-03-25 21:39:45 -07:00
0cae61444d New Condition Widget, WIP
- Refine base styling;
- Fix so that edited properties are reactive;
2020-03-25 19:47:21 -07:00
28a603def8 New Condition Widget, WIP
- Add condition-widget.scss file;
- Add type definitions to control display behaviors in layouts;
- Refine widget structure;
2020-03-25 18:24:14 -07:00
d2b7407674 add request for LAD data 2020-03-25 17:41:33 -07:00
fff89a6384 delete unsubscribe method on destroy 2020-03-25 17:36:27 -07:00
953b95f79c Merge branch 'topic-conditionals' into new-widget 2020-03-25 17:22:43 -07:00
6ff5ce78e1 Merge remote-tracking branch 'origin' into new-widget 2020-03-25 17:16:23 -07:00
0857fd95a7 New Condition Widget, WIP
- Rename `c-object-view` to `u-angular-object-view-wrapper` and fix to
allow style settings to be applied to it;
2020-03-25 17:15:56 -07:00
a1f2608e7c Merge pull request #2782 from nasa/conditionals-ch-fixes-032420
Misc Conditionals fixes
2020-03-25 17:14:08 -07:00
b7cea7b955 New Condition Widget, WIP
- Normalize usage of `c-object-label` across components;
2020-03-25 16:34:11 -07:00
32da19a486 New Condition Widget, WIP
- Add new Vue components, mod configs;
2020-03-25 16:19:46 -07:00
39d7dc8372 Adds unit tests for isOneOf and isNotOneOf 2020-03-25 15:50:16 -07:00
f76f537be7 removed extraneous emits and handlers 2020-03-25 15:27:48 -07:00
a681d67e05 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionset-multi-value-operations 2020-03-25 14:14:23 -07:00
0a634eb490 Fixes unit tests 2020-03-25 14:08:33 -07:00
fcca8fa8d9 Fixes error while getting metadata when telemetry is not yet available for a criterion 2020-03-25 13:44:26 -07:00
829eecf1ae resolved merge conflicts 2020-03-25 13:24:43 -07:00
dc7f83754a Adds parse treatment for string format.
Adds format property to sine wave generator's name metadata
2020-03-25 12:05:24 -07:00
51498c0e75 Merge pull request #2781 from nasa/global-filters-no-request-bug-fix
Global Filters not re-requesting on value change
2020-03-25 11:57:30 -07:00
6dbdebbe05 Merge branch 'master' into global-filters-no-request-bug-fix 2020-03-25 11:54:45 -07:00
c994227d5d Merge pull request #2769 from nasa/telemetry-table-duplicate-request-fix
[Telemetry Tables] Duplicate requests when users change time system
2020-03-25 11:42:34 -07:00
761ca7ad56 remove console log 2020-03-25 11:16:25 -07:00
7f49a7bc99 Conditionals fixes and tweaks
- Fixes #2772;
- Remove styling toolbar icons in Display Layouts;
- Fix regression error in tree items that was only allowing clicks on
the name to navigate;
2020-03-25 11:12:54 -07:00
ca022b8a28 make a copy of filters before checking isEqual
- to prevent using same pointer
2020-03-25 11:11:17 -07:00
6f500d0d0b Resolves ##2741
Uses parsed telemetry data values for comparisons
2020-03-25 10:45:28 -07:00
07d101ac1c Conditionals fixes and tweaks
- Legacy wrapper fixes to allow color styling to be applied as expected;
- New `$mainViewPad` constant;
- Fix `c-control-bar` element spacing in plots;
- TODO: check for regressions, particularly with plot views;
- Fixes #2772: better `is-style-invisible` css for `c-style-thumbs`;
2020-03-24 23:45:25 -07:00
cdf0dd0c10 Conditionals fixes and tweaks
- Fixes multiple items in #2772;
- Fix missing wrapping markup, convert empty span to template;
- Add INPUT_TYPES object to allow proper setting of 'text' input type;
- Fix padding on <selects> for better inline alignment with other input
types;
2020-03-24 19:41:31 -07:00
c27c347d29 Conditionals fixes and tweaks
- Fixes #2778;
2020-03-24 18:17:13 -07:00
dc54eef2c9 Resolves #2776
Adds null checks for telemetry and domain object configuration
2020-03-24 15:40:33 -07:00
57a68a24de Apply condition sets to drawing objects in a layout + Static styling for objects. (#2767)
* Hardcoded prototype - conditional styles for display layout or generator domain objects only.
Needs Architectural review

* Updates to ConditionalStylesView

* Adds background colors list

* Show conditional styles for subObjectViews and telemetryViews

* Uses telemetry provider to determine which style is active
Removes hardcoded conditionSet work used for prototype

* Fixes failing test

* Add default styles to conditionalStyles when a condition set is added to the domain object.

* Use EventEmitter alias instead of eventEmitter3 in imports
Change variable name for better readability and clarity
Remove unused code

* Change StyleRuleManager to accept conditionStyle objects instead of domainObjects

* Uses a map for conditional styles instead of a list in order to improve performance

* Use in-built api to check for identifier equality
Pass missing arguments

* Removes unnecessary object get call.

* Conditionals styling WIP

- Tweaks for layout/overflow issues;
- Better header alignment;
- Much better approach to "accordion" spacing;

* Adds conditional styles for drawing objects

* Fixes small bugs while adding or removing nested conditional styling

* Removes hard coded conditionSetIdentifier

* Fixes small conditionManager bug

* WIP - Adds condition set selection dialog

* WIP - Add condition set

* Styling for tree dialog in selector

- New `.c-selector` class;
- Markup added to tree-item.vue to fix missing `c-object-label` wrapper;

* Adds image url to styles inspector view

* Adds highlight for selected conditionSet in selection dialog.

* Conditionals Inspector styling WIP

- Simplified Inspector markup;
- Refined `.c-inspector` layout strategy;
- Refined `.l-multipane` classes for better layout and scroll handling;
- TODOs: styling for 'styles' section; unit test, find regressions, fix;

* Read-only mode changes for the styles inspector

* Adds basic styling for styles inspector view

* Removes unused code

* Displays condition set name

* Display description for conditions

* Conditionals Inspector styling WIP

- Add new ObjectName component to Inspector;
- Add supporting styles;

* Conditionals Inspector styling WIP

- Refactor for better usage of c-object-label;
- Refactor `c-properties` classes to `c-inspect-properties`;

* Conditionals Inspector styling WIP

- Introduce `c-inspect-styles` class;
- Markup refined in ConditionalStylesView.vue;

* Conditionals Inspector styling WIP

- Fix unintended regression in `c-object-label`;

* Makes inspector view tabs sticky.
Shows errors if either telemetry or conditions are missing
Refresh conditionStyles when styles inspector is loaded to show new conditions that might be added to the conditionSet

* Conditionals styling WIP

- Added title to Condition Set selector dialog;

* Conditionals styling WIP

- CSS and markup changes;

* Conditionals styling WIP

- Markup changes;

* Componentize conditional style editor, condition description and condition error

* Conditionals styling WIP

- Conditional styling elements in Inspector, significant refinements;
- Condition description formatting;
- Significant markup simplification in StyleEditor.vue;
- Moved `.c-inspect-styles` into conditional-styles.scss;

* Conditionals styling WIP

- Added menu positioning css;

* Conditionals styling WIP

- Display images in StyleEditor `c-style-thumb` element;

* Use the condition description component to show the condition description in condition set views

* Conditionals styling WIP

- New `u-alert` and `u-error` classes and mixin;
- New $colorError theme constants;

* Conditionals styling WIP

- Fixed background: cover or c-style-thumbs;

* Conditionals styling WIP

- Add navigate-or-preview functionality to attached Condition Set used
for conditional styling (thank you Deep!);
- Better theme-compliant box-shadowing on c-style-thumb;
- Added default color property to cClickIcon SCSS mixin;

* Fixes ConditionDescription and ConditionErrors views

* Allow static styles for domain Objects and display layout items

* Consolidate handling of 'none' style to the object styles mixin

* Adds visibility option to child items

* Conditionals styling WIP

- Add `is-style-invisible` class;
- Better color approach for table headers to support domain object
styling;

* Change visibility to isInvisible class

* Fixes visibility for object views and telemetry views

* Fixes selection of condition set from tree view

* Conditionals styling WIP

- Added new $glyph-icon-eye-disabled font symbol and related class;
- Modded glyphs used for visibility toggle button;
- Modded StyleEditor thumb preview to reflect visibility setting;
- Tweaked `is-style-invisible` opacity property;
- Code cleanup;

* Conditionals styling WIP

- Display disabled toolbar for Inspector styles in Browse mode;
- Better approach to 'is-style-invisible' `c-style-thumb` with new
bgCheckerboard mixin;
- Moved `$controlDisabledOpacity` into a theme constant;
- Refined spacing in Inspector grid;

* Fixes linting issues

* Fixes telemetry metadata name

* criterion descriptions is now an array of strings

* Fixes spacing

* Removes commented out code.

* Remove v-if

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-03-24 13:09:21 -07:00
2c1b4b4cfc Merge pull request #2759 from nasa/dave/conditions-telemetry
[Conditions] Request, Subscribe to, and Provide Telemetry
2020-03-24 11:43:09 -07:00
437e8a0263 always load composition 2020-03-24 11:15:58 -07:00
0a2e912091 removed refs from dom elements and added index to call to isLastCriterion 2020-03-24 09:43:54 -07:00
87513a14b7 removed log 2020-03-23 17:47:35 -07:00
168c040f3c fix sticky telemetry selection issue 2020-03-23 16:16:58 -07:00
78487a48f6 restrict conditionManager instances to one per domainObject 2020-03-23 16:04:37 -07:00
a5a197680d remove debug 2020-03-23 15:58:01 -07:00
d42bd44485 remove logging statement 2020-03-23 15:24:24 -07:00
77b705ecc8 add telemetry object back for criterion 2020-03-23 15:21:51 -07:00
48af39a584 Merge branch 'topic-conditionals' into dave/conditions-telemetry 2020-03-23 14:58:32 -07:00
ec978f3a35 when a time system is changed, it emits a timeSystem update as well as a bounds update, this caused telemetry table to request data twice when users changed time systems 2020-03-23 14:21:46 -07:00
f13714e0c4 rename telemetry subscription calls 2020-03-23 13:53:19 -07:00
42ac3ef9af Implements two new operations - is one of and is not one of that evaluates to true if telemetry matches one of many values 2020-03-23 13:21:22 -07:00
26ffe8efde remove comment 2020-03-23 13:05:48 -07:00
87b4000b12 Merge pull request #2763 from nasa/dave/conditions-views
[Conditions] Enable dropping Condition Sets into overlay plots
2020-03-23 12:44:48 -07:00
f790c9bd39 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into topic-conditionals 2020-03-23 11:55:33 -07:00
096c9688d5 Merge branch 'master' of https://github.com/nasa/openmct into topic-conditionals 2020-03-23 11:55:12 -07:00
1f19f480ce fix unit tests 2020-03-20 17:38:29 -07:00
44f48a3e2a moved persist calls to events, used emit to save name and fieldname props 2020-03-20 17:37:48 -07:00
60fce4a003 remove completed TODO comments 2020-03-20 17:08:55 -07:00
f04b5b689e suppress plot view on view condition sets 2020-03-20 17:08:23 -07:00
05d981768e change string output to enum for plot views 2020-03-20 17:07:51 -07:00
e4a6c21101 load composition using less code 2020-03-20 15:08:45 -07:00
d51dd8b7d0 fix broken tests 2020-03-20 15:00:24 -07:00
aed5377ad2 use existing var instead of recalculating
on listener should be off in destroy()
2020-03-20 11:44:05 -07:00
459b2060a5 Merge pull request #2727 from nasa/swg-randomness
Adds customizable randomness factor to sine wave generator
2020-03-20 10:25:17 -07:00
cbeb25c583 request telemetry from all conditions
if conditions have no criteria (isDefault) then nothing will be requested
2020-03-20 10:16:02 -07:00
b38a9ad4ce exclude default condition when requesting telemetry 2020-03-20 10:12:02 -07:00
ecf3e19f16 remove listener on destroy 2020-03-19 20:30:27 -07:00
3b82fd5d8b Merge branch 'topic-conditionals' into dave/conditions-telemetry 2020-03-19 20:28:40 -07:00
e08b4ff0ab Merge pull request #2751 from nasa/fix-enum-metadata
Handles enum meta data for criteria
2020-03-19 20:18:35 -07:00
1f3ec77bf1 Merge branch 'topic-conditionals' into fix-enum-metadata 2020-03-19 20:15:05 -07:00
3f61db2067 pass telemetry down to criteria. criteria listens to corresponding endpoints. 2020-03-19 17:30:02 -07:00
ce1fdbddda Merge pull request #2757 from nasa/conditions-set-cleanup
[Conditions] Condition Set cleanup
2020-03-19 15:19:13 -07:00
5332d136b7 Fix small bug with inputCount 2020-03-19 15:16:12 -07:00
983ed7f0e7 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into fix-enum-metadata 2020-03-19 14:06:25 -07:00
11978cd869 remove straggler call
linting fix
2020-03-19 10:22:48 -07:00
00d1b5e69f avoid multiple instantiations of condtionManager if possible 2020-03-19 10:18:57 -07:00
6731283cf8 Merge pull request #2734 from nasa/condition-description
Condition dynamic summary description based on criteria
2020-03-18 15:25:19 -07:00
6b4cd25417 Merge branch 'topic-conditionals' into dave/conditions-telemetry 2020-03-18 10:58:17 -07:00
0cd2799d00 added isLastCriterion comp method 2020-03-18 10:24:43 -07:00
243b9cac24 subscribe to telemetry 2020-03-18 10:21:53 -07:00
316e0f24cf added fieldName prop to telemetry object, computed props for canEvaluateCriteria, getConjunction, simplified getRule method 2020-03-18 10:18:42 -07:00
05f94edb49 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into topic-conditionals 2020-03-18 09:29:10 -07:00
e22458f09e Merge branch 'master' of https://github.com/nasa/openmct into topic-conditionals 2020-03-18 09:28:53 -07:00
cc2df8401b vue style render of description 2020-03-17 17:36:55 -07:00
43a82ec05f Conditional styles for drawing objects (#2740)
* Hardcoded prototype - conditional styles for display layout or generator domain objects only.
* Adds background colors list
* Show conditional styles for subObjectViews and telemetryViews
* Uses telemetry provider to determine which style is active
* Removes hardcoded conditionSet work used for prototype
* Add default styles to conditionalStyles when a condition set is added to the domain object.
* Use EventEmitter alias instead of eventEmitter3 in imports
* Change StyleRuleManager to accept conditionStyle objects instead of domainObjects
* Uses a map for conditional styles instead of a list in order to improve performance
* Use in-built api to check for identifier equality
* Removes unnecessary object get call.
* Adds conditional styles for drawing objects
* Removes hard coded conditionSetIdentifier
* Fixes small conditionManager bug
2020-03-17 14:42:15 -07:00
fe2e29d69b Merge pull request #2753 from nasa/remove-ConditionSetView-telemetry
Remove telemetry subscription from conditionSet edit view
2020-03-17 13:49:25 -07:00
60aecfe27e Merge branch 'topic-conditionals' into dave/conditions-telemetry 2020-03-17 12:00:57 -07:00
5d21a8b6fe Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into remove-ConditionSetView-telemetry 2020-03-17 11:06:51 -07:00
500ab52476 Merge pull request #2754 from nasa/conditions-observables
[Conditions] Remove uneccessary update function
2020-03-17 11:05:46 -07:00
b0bb723357 resolve merge, address review comments 2020-03-17 10:31:19 -07:00
b7fffeab1c conditionManager controls subscriptions 2020-03-16 21:57:42 -07:00
e339d743ed remove uneccessary update call 2020-03-16 16:01:12 -07:00
84f0d49d6f code is documentation 2020-03-16 15:35:13 -07:00
090e89d524 remove testing aid 2020-03-16 15:34:12 -07:00
90dd53e954 Merge branch 'topic-conditionals' into dave/condition-telemetry-request 2020-03-16 15:31:19 -07:00
6ab60ab52e ConditionSet view listens to a listener from conditionCollection to display current output. 2020-03-16 15:00:16 -07:00
8975bc8c55 Handles enum meta data for criteria
Ensures metadata with sources are also handled correctly
2020-03-16 13:36:33 -07:00
55e5c49f6e Merge pull request #2747 from nasa/conditions-do-refactor
Conditions refactor - not domain objects anymore
2020-03-16 10:59:31 -07:00
f090f7ffe7 Removes comment. 2020-03-16 10:51:33 -07:00
94b5617e63 Since we're observing for changes to the conditionSet domain object, we don't need to re-fetch it with persist. 2020-03-16 10:50:11 -07:00
41c79c6032 [Conditions] ConditionManager should observe its own mutations (#2748)
* change blur listener to change listener to watch for immediate updates to input

* move observe ConditionManager logic into ConditionManager
2020-03-16 10:14:46 -07:00
83c648cc26 Addresses comments 2020-03-13 14:24:47 -07:00
76e7fec1a0 Ensures the conditionManager is in sync with outside changes so that composition is not thrown away. 2020-03-13 14:08:14 -07:00
09bfd80f69 Fix bug with telemetry disappearing 2020-03-13 12:50:06 -07:00
15a7d03e74 rename function for clarity 2020-03-13 09:36:17 -07:00
1dc9743484 remove unecessary get call 2020-03-13 09:26:46 -07:00
8f05c57d1a Fixes clone condition bug 2020-03-13 09:09:50 -07:00
81caa27cba Fixes failing tests 2020-03-12 14:38:39 -07:00
74a7ef2565 Fix bug with removeCondition 2020-03-12 14:06:43 -07:00
649575fd2d Moves domain object observe logic to the condition set telemetry provider 2020-03-12 13:29:50 -07:00
b75b7a958a added input types and fixed bug for isDefined, isUndefined comparisons 2020-03-12 13:14:54 -07:00
625b39d722 Fix typo 2020-03-12 13:06:56 -07:00
65f80f4c45 Resubscribe to the conditionSet telemetry provider when the condition set domain object changes. 2020-03-12 13:05:03 -07:00
02cd3048c8 Removed conditions as domain objects 2020-03-12 12:33:04 -07:00
63feaef988 reinitialize rule string in forEach 2020-03-11 15:22:17 -07:00
6095872682 remove logging 2020-03-10 10:08:53 -07:00
dba55867f4 fix broken unit tests 2020-03-10 09:59:12 -07:00
0da80c2a67 move var declaration around for clarity 2020-03-10 09:58:57 -07:00
084df5329a Merge branch 'topic-conditionals' into dave/condition-telemetry-request 2020-03-09 16:33:55 -07:00
49ff0c79db tidy up condition manager use in telemetry provider 2020-03-09 16:15:53 -07:00
7a4b967a01 condition sets with no telemetry points should not provide telemetry 2020-03-09 13:28:37 -07:00
ddfa611c44 Merge branch 'master' into swg-randomness 2020-03-09 13:11:31 -07:00
efca7c8e58 added descriptions to conditions 2020-03-09 12:42:44 -07:00
8900072239 added computed property and method to get description 2020-03-09 11:50:11 -07:00
a7e57c62f4 linting fixes (#2733) 2020-03-09 11:15:15 -07:00
24bade2284 provide correct data to getLatestTimestamp call 2020-03-09 10:59:05 -07:00
5fcc4eebe1 Add a re-calculate column width button (#2719)
* Add a recalculate Column width button

* Tweaks to telemetry table for recalculateColumnWidths

- Recalc button now hidden if isAutosizeEnabled === false;
- Recalc button label, title edited for clarity;
- Normalized button titles for other table buttons;
- Fixed `.c-separator` height issue;

* toggle between expand and autosize

* Tweaked button title text

* remove nested loop

* fix lint errors

* remove unecessary promise and use clientWidth instead of offsetWidth

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-03-09 10:31:26 -07:00
27a09239e3 Integrate Conditionals styling into topic-conditionals (try 2) (#2730)
* Styling for Conditionals WIP

- Condition Set markup and styling WIP;

* Styling for Conditionals WIP

- Condition Set markup and styling WIP;

* Styling for Conditionals WIP

- Main layout and container styling refinement, simplification and
normalization;

* Styling for Conditionals WIP

- Begin styling for individual condition elements;

* Styling for Conditionals WIP

- Condition styling, very initial;

* Conditionals styling WIP

- Redo work done previously and lost due to merging;
- Overall layout in edit mode;
- Styling for hint element;

* Conditionals styling WIP

- Major progress on Conditionals edit-view styling;
- Grid layout WIP in condition element;
- Added new `.c-grippy` class;

* Conditionals styling WIP

- discreteItem theme constants refined, add
`$colorDiscreteItemCurrentBg` color value;
- `.c-grippy` enhanced;
- Condition layout significantly refined;

* Conditionals styling WIP

- Styling for browse view in Condition Set;
- Refined alignment and styles for condition header;

* Conditionals styling WIP

- Cleanups;
- Significant improvements to flex layout;
- Test Data layout and element formatting;

* Conditionals styling WIP

- Better approach to condition set hinting;

* Conditionals styling WIP

- Merge and integrate changes from Joel;
- 'Add Criteria' button now disabled until telemetry has been added;
- Fix JS configuration error with help from Joel;

* Conditionals styling WIP

- Much better flex approach to sections layout;
- Sanding and shimming;

* Conditionals styling WIP

- Fixed some linting;

* Conditionals styling WIP

- Tweaks for layouts issues;
2020-03-09 10:08:39 -07:00
8d86c914a1 Merge pull request #2732 from nasa/minmax-fix-382020
Revert minMax to minmax
2020-03-09 10:05:42 -07:00
fab04519c6 fix minmax typo 2020-03-08 20:11:04 -07:00
4a5e106709 request LAD for conditions provides telemetry 2020-03-06 17:33:21 -08:00
4675fc8ae6 Conditional styles (#2718)
* Hardcoded prototype - conditional styles for display layout or generator domain objects only.
Needs Architectural review

* Updates to ConditionalStylesView

* Adds background colors list

* Show conditional styles for subObjectViews and telemetryViews

* Uses telemetry provider to determine which style is active
Removes hardcoded conditionSet work used for prototype

* Fixes failing test

* Add default styles to conditionalStyles when a condition set is added to the domain object.

* Use EventEmitter alias instead of eventEmitter3 in imports
Change variable name for better readability and clarity
Remove unused code

* Change StyleRuleManager to accept conditionStyle objects instead of domainObjects

* Uses a map for conditional styles instead of a list in order to improve performance

* Use in-built api to check for identifier equality
Pass missing arguments

* Removes unnecessary object get call.
2020-03-06 14:09:52 -08:00
cf9336dae9 WIP receive criterion results and compute condition results 2020-03-06 11:01:15 -08:00
7f32d196e4 WIP request gets to telemetry criterion before erroring 2020-03-05 23:11:20 -08:00
897d05276a WIP continue to add support for LAD request
TODO conditionCollection needs to load before condition requests can be made
2020-03-05 16:42:31 -08:00
3e6509ce6f WIP add support for LAD request 2020-03-05 12:14:27 -08:00
576b843bd5 Merge branch 'master' of https://github.com/nasa/openmct into topic-conditionals 2020-03-05 10:00:54 -08:00
9b8b63a0d8 Revert record size limit to 5000 2020-03-05 08:56:47 -08:00
a1b1fa464e Adds customizable randomness factor to sine wave generator. Fixes #2726 2020-03-05 08:55:05 -08:00
95f855f905 Merge pull request #2717 from nasa/dave/conditions-telemetry
[Conditions] Use latest incoming telemetry timestamp for providing telemetry
2020-03-04 12:05:33 -08:00
5b00246cc0 fix failing unit test 2020-03-04 12:00:42 -08:00
47c388450f Merge pull request #2707 from nasa/alternate-control-bar-fixes
[Telemetry Tables] - Alternate control bar fixes
2020-03-04 11:54:38 -08:00
34a149661c keep track of latest timestamp 2020-03-03 17:34:27 -08:00
4c4b587d9c pass timestamp datum through to ConditionManager 2020-03-03 14:37:11 -08:00
b8b838f490 get all timesystems 2020-03-03 12:50:40 -08:00
8cb29ba4a9 add handleConditionUpdated back 2020-03-03 11:34:30 -08:00
ece6223b23 Merge pull request #2704 from nasa/dave/provide-conditions-telemetry
[Conditions] Conditions to provide telemetry
2020-03-02 14:56:10 -08:00
ecabd00b0c fix failing unit tests 2020-03-02 14:48:24 -08:00
768df84f10 Merge pull request #2711 from nasa/condition-output-persistance2
Added persistance for changes to output selector
2020-03-02 13:56:31 -08:00
f8b3899bb9 addressed review comments and lint errors 2020-03-02 12:30:28 -08:00
3b046db4f8 Merge pull request #2695 from nasa/firefox-drag-fix
Add setData on dragStart for FF support
2020-03-02 11:57:45 -08:00
97f829da9f added dragstop listener and dragStop method 2020-03-02 11:46:16 -08:00
fb1eed1982 Merge pull request #2706 from nasa/current-output-refactor
Refactored CurrentOutput component into ConditionSet
2020-03-02 11:28:51 -08:00
dd9b567025 revert alternate control bar for telemetry table 2020-03-02 11:09:50 -08:00
fa83b4867c removed extra div 2020-03-02 06:17:27 -08:00
47d4fc9103 removed CurrentOutput component 2020-02-28 15:32:40 -08:00
dabd0bff29 only listen for condition updates in telemetry provider 2020-02-28 15:18:14 -08:00
51c70d02d7 fix naming bug 2020-02-28 15:17:27 -08:00
b74733bf3f Add back id and conditionId required for styling 2020-02-28 15:05:27 -08:00
84ae65536b remove unused var 2020-02-28 14:40:12 -08:00
71424dcf8d Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into firefox-drag-fix 2020-02-28 14:39:02 -08:00
2c40396139 refactor for clarity 2020-02-28 14:35:57 -08:00
16a0bf9d6c Merge pull request #2703 from nasa/criterion-multi-input
Criterion multiple input fields
2020-02-28 14:07:40 -08:00
5498ba8e1e changed computeResults 2020-02-28 13:51:46 -08:00
0f9d7d2832 fix default condition edge case 2020-02-28 13:10:06 -08:00
9bd1c51a6e always use timestamp from latest datum 2020-02-28 13:06:57 -08:00
fd74fb0ec4 remove unused var 2020-02-28 12:50:17 -08:00
3626ff9947 fix linting issues 2020-02-28 12:47:34 -08:00
fd568409d3 Merge branch 'topic-conditionals' into dave/provide-conditions-telemetry 2020-02-28 12:28:59 -08:00
14e3500a88 add timestamps for default condition 2020-02-28 12:27:51 -08:00
83d08ae369 resolve merge conflicts 2020-02-28 12:17:12 -08:00
39bf601ee1 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into firefox-drag-fix 2020-02-28 11:43:17 -08:00
cfafecdd64 Merge pull request #2685 from nasa/conditionals-refactor
Conditionals refactor
2020-02-28 11:35:09 -08:00
629ca089cf Fixes for table's alternateControlBar
- Fixed hide/show of controls for better UX;
- Unit tested click select/deselect toggling;
2020-02-28 11:31:23 -08:00
89ae6ef8c7 provide telemetry with timestamps 2020-02-28 11:15:19 -08:00
300acd6ec8 remove row if user unmarks in showMarkedRowsOnly mode 2020-02-28 11:12:49 -08:00
ba780981a5 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionals-refactor 2020-02-28 11:09:41 -08:00
62774678a7 enable alternatte control bar in telemetry tables for charles 2020-02-28 11:04:34 -08:00
ac13bc5850 Merge pull request #2688 from nasa/condition-trigger
Condition Any/All criteria match enabled
2020-02-28 10:53:02 -08:00
e526626e09 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into condition-trigger 2020-02-28 10:49:56 -08:00
564be6f8ba Merge branch 'condition-trigger' of https://github.com/nasa/openmct into condition-trigger 2020-02-28 10:05:11 -08:00
371a7d3a3e removed disabled inputs with no telemetry 2020-02-28 10:04:10 -08:00
8539d60562 enable trigger 2020-02-28 10:01:37 -08:00
d333fd5822 disabled add or edit condition set without telemetry 2020-02-28 10:01:37 -08:00
364191eddc Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionals-refactor 2020-02-28 09:57:00 -08:00
583f4dac85 Merge branch 'conditionals-refactor' into dave/provide-conditions-telemetry 2020-02-28 09:56:25 -08:00
28255dce01 Merge pull request #2687 from nasa/remove-criteria
Added controls for remove and duplicate criteria
2020-02-28 09:53:53 -08:00
c9419d3e2d Fix for when input is not an array. This will be handled differently in an upcoming code change. 2020-02-28 09:47:58 -08:00
b386275330 removed disabled inputs with no telemetry 2020-02-28 08:55:59 -08:00
d2a45e46f1 modified getOperationFormat 2020-02-28 08:37:20 -08:00
35d1727dbf Merge pull request #2696 from nasa/marking-improvement-tables
An improvement for components using telemetry tables
2020-02-27 17:20:08 -08:00
8125a4f321 emit event when rows are marked - useful for other components using telemetry tables 2020-02-27 17:05:02 -08:00
1a409afb03 wip: addressing review comments 2020-02-27 16:32:38 -08:00
e57c18fd69 removed unused isDefault prop 2020-02-27 14:55:52 -08:00
3aec9ec6ff add setData on dragStart for FF support 2020-02-27 14:36:21 -08:00
0e9bf74332 Merge branch 'conditionals-refactor' into dave/provide-conditions-telemetry 2020-02-27 13:36:46 -08:00
2609a41ee8 Addresses comments
Removes console log statements
Fixes import paths
2020-02-27 11:21:40 -08:00
b8dc5acf00 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionals-refactor 2020-02-27 11:19:35 -08:00
fbbdf8cff7 Merge pull request #2607 from krynju/f#2594
Time x-axis tick labels reversed - fix #2596
2020-02-27 11:11:05 -08:00
b0edb19239 Merge branch 'master' into f#2594 2020-02-27 11:06:59 -08:00
85902b878e Update telemetry table for multisession (#2686)
* update telemetry table to ingest marked row data, add a new alterntate bar with includes row name, selected rows and show selected rows toggle

* Enhancements for alternate toolbar in telem tables

- .c-control-bar adds style enhancements and `__label` element;
- Added `label` prop, markup and styling to ToggleSwitch;
- ToggleSwitch layout enhanced;
- Unit tested in main view and placed in Display Layout;

* made improvements to row marking

* bug fixes for marking

* fix linting issues

* -Make reviewer requested changes
-Clarify prop for marking
-Include alternateControlBar in the marking prop
- - since it only makes sense for making

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
2020-02-27 10:27:57 -08:00
9d5c7a4015 destroy conditionManager instance on stop providing telemetry 2020-02-26 15:26:39 -08:00
fc53e855c4 Merge branch 'conditionals-refactor' into dave/provide-conditions-telemetry 2020-02-26 15:15:22 -08:00
467c57b7c6 Merge branch 'criterion-multi-input' of https://github.com/nasa/openmct into criterion-multi-input 2020-02-26 12:32:21 -08:00
a51c0d5139 addressed review comments 2020-02-26 12:31:37 -08:00
d46310ca7d handle multiple inputs in telemetryCriterion 2020-02-26 12:31:37 -08:00
8f87cc78e8 rendering correct number of input fields depending on operation and persisting values 2020-02-26 12:31:37 -08:00
ee6e0f310e changed default input to array and clear downstream selection lists on change 2020-02-26 12:31:37 -08:00
f328a1078e filter comparison list based on field selected 2020-02-26 12:31:37 -08:00
b4cf81a0ef wip: filtering select comparison 2020-02-26 12:31:37 -08:00
1b9b7e2345 added hid/show selects 2020-02-26 12:31:37 -08:00
4456633010 output telemetry to views 2020-02-26 12:27:28 -08:00
cda97d142a Merge branch 'condition-trigger' of https://github.com/nasa/openmct into condition-trigger 2020-02-26 12:23:03 -08:00
858199e396 enable trigger 2020-02-26 12:22:29 -08:00
f504a335af disabled add or edit condition set without telemetry 2020-02-26 12:22:29 -08:00
463ec47af6 changed classname of controls div 2020-02-26 12:21:53 -08:00
ec4d121a98 changed classname of controls div 2020-02-26 11:59:04 -08:00
fea6d2df96 Merge branch 'remove-criteria' of https://github.com/nasa/openmct into remove-criteria 2020-02-26 11:53:09 -08:00
598d2b31e9 remove criterion working 2020-02-26 11:51:41 -08:00
25e28ab97c added controls for remove and duplicate criteria 2020-02-26 11:49:17 -08:00
43056c4068 implimented clone criterion 2020-02-26 11:42:06 -08:00
614206b10c remove criterion working 2020-02-26 11:42:06 -08:00
30a493d038 resolved merge conflict 2020-02-26 11:40:52 -08:00
96e433beaa Merge pull request #2677 from nasa/condition-clone-name
Condition clone name persistance
2020-02-26 11:32:48 -08:00
0915aaea3b allow conditions to be dropped into telemetry views 2020-02-26 11:31:26 -08:00
80656c1be0 Merge branch 'topic-conditionals' into dave/provide-conditions-telemetry 2020-02-26 11:11:48 -08:00
acd75f86f4 add name to conditions view 2020-02-26 11:07:25 -08:00
486dae54bd add metadata provider 2020-02-25 17:27:08 -08:00
92ecf3af1d provide telemetry
* listen to realtime data
2020-02-25 16:37:43 -08:00
fd0c19026d addressed review comments 2020-02-25 15:36:37 -08:00
3109c8d825 Destroy condition manager when the ConditionSet vue is destroyed 2020-02-25 15:06:45 -08:00
78cf75323f Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionals-refactor 2020-02-25 12:31:44 -08:00
b744467f21 Bug fixes:
Ensures that the default condition emits a condition result update
Fixes remove condition
2020-02-25 12:29:47 -08:00
a0b7999ea2 Imagery fixes (#2668)
* Fix imagery-related styles and markup

- VERY WIP!!!
- Style modernizing;
- Also, padding fixes for pane.scss - unit test for regressions!

* Fix imagery-related styles and markup

- VERY WIP!!!
- Style modernizing WIP;
- Fixes to pane classes for better padding in vertical panes;

* Fix imagery-related styles and markup

- Migrated all imagery CSS into imagery-view-layout.scss from _legacy
.scss;
- CSS cleanups;
- Refactoring/simplification of thumb layout;
- Color fixed for $colorPausedFg in theme constants;

* Scroll to right instead of bottom, on autoscroll.

* Fix imagery-related styles and markup

- Make the most recent thumb visually distinct;
- Clicking a selected thumb now deselects it and unpauses the view;

* Imagery fixes

- Fixed thumb click logic to properly toggle paused when clicking a selected thumb;
- Improved CSS so that `selected` updates more quickly when selecting the latest thumb;
- Clicking the main image pause button now selects the proper thumb;

* Fix linting errors

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2020-02-25 11:47:27 -08:00
2bb2bb6a1b Adds conditional style inspector provider (#2655)
* resolved conflicts

* Revert "Condition reorder"

* Adds conditional style inspector provider
Adds condition style tabbed view (this needs to be extended to allow more than one pane per tab in a separate issue)

* Fixes linting issues

* Merge from topic-conditionals

* In order to accomodate two tab layout for the Conditionals feature, rename the following:
- openmct.inspectorViews registry to openmct.propertiesInspector
- InspectorViewRegistry.prototype.addProvider to InspectorViewRegistry.prototype.addViewProvider.
Replace occurances of the same to the new names.

In a subsequent commit expect a new view registry for the styles inspector view registry.

* Use 'styles' property on domain objects to indicate that they should have a styles inspector tab - Note that this will not show up on existing objects but only ones that are created after this feature is added.
Use 'styles' property on domain objects to determine if a styles view can be viewed
Removed the TabbedInspectorView and repurposed the InspectorViews to show both a properties or a styles registry.
Simplified markup in Inspector.vue

* Addresses review comments:
1. Go back to using inspectorViews
2. Remove stylesInspector registry
3. Hardcode Styles Inspector component view
4. Styles tab can be viewed for all creatable objects except for the folders, webPages and conditionSets
5. ConditionalStylesInspectorViewProvider is no longer needed because we are hardcoding the styles view component.

Co-authored-by: Joel McKinnon <JoelMcKinnon@users.noreply.github.com>
2020-02-25 10:41:58 -08:00
11ed7027e7 enable trigger 2020-02-25 09:31:29 -08:00
36bcfd5a41 disabled add or edit condition set without telemetry 2020-02-25 08:25:02 -08:00
70b5c627ca handle multiple inputs in telemetryCriterion 2020-02-24 16:48:27 -08:00
f4f1d0387b rendering correct number of input fields depending on operation and persisting values 2020-02-24 16:31:59 -08:00
a1bf4a92e5 changed default input to array and clear downstream selection lists on change 2020-02-24 15:50:01 -08:00
7d2256d70f Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionals-refactor 2020-02-24 15:44:07 -08:00
5814d2a35e Updates conditionManager.js to emit the output of the winning condition instead of the identifier of that condition 2020-02-24 15:43:03 -08:00
6ab84c0bc3 Refactoring conditionals
Move logic for conditions into conditionManager out of conditionCollection.vue
2020-02-24 15:05:33 -08:00
a7fc9b3caa filter comparison list based on field selected 2020-02-23 15:19:03 -08:00
67f493f012 rebased with topic-conditionals and resolved conflict 2020-02-23 11:30:01 -08:00
0686e6d38f implimented clone criterion 2020-02-23 11:26:58 -08:00
7fc825949c remove criterion working 2020-02-23 11:26:58 -08:00
2a9ccdcffd added controls for remove and duplicate criteria 2020-02-23 11:26:58 -08:00
6db78af69f fixed condition name persistance 2020-02-23 09:58:32 -08:00
038489256c updated Condition.vue with modifications to ConditionEdit.vue and deleted ConditionEdit as it is no longer used 2020-02-23 09:06:37 -08:00
53b785269b wip: filtering select comparison 2020-02-21 06:43:16 -08:00
007b14b5c9 added hid/show selects 2020-02-20 14:59:04 -08:00
50b331c451 fixed condition clone 2020-02-20 14:45:30 -08:00
44fc62e0ba fix reordering 2020-02-20 14:17:15 -08:00
2635f085f0 Merge pull request #2665 from nasa/add-criteria
Add multiple criteria and persist inputs
2020-02-20 13:51:31 -08:00
22161fce7f implimented clone criterion 2020-02-20 12:44:50 -08:00
386fc75047 remove criterion working 2020-02-20 12:23:18 -08:00
fa6dd84945 added controls for remove and duplicate criteria 2020-02-20 07:23:38 -08:00
d425bd564c fixed merge conflicts 2020-02-19 13:29:14 -08:00
93e3065b3e Fixes subscription of telelemtry when new criteria is added to a condition 2020-02-19 12:51:24 -08:00
0ad2d59924 Fixes tests 2020-02-19 12:51:15 -08:00
f4468a8233 Fixing condition output 2020-02-18 16:04:14 -08:00
dc08877bbb Persists the condition domain object so that we can retrieve it with it's identifier in child components like condition.vue 2020-02-18 15:35:03 -08:00
f08caa6135 addressed review comments 2020-02-18 12:40:33 -08:00
ad7d029ce8 addressed review comments 2020-02-18 11:14:50 -08:00
387912b4d3 WIP fixing telemetry subscribe 2020-02-18 11:05:54 -08:00
53e0ed4d4a refactored to address review comments 2020-02-14 15:23:15 -08:00
11c205b5c4 addressed review comments 2020-02-14 14:46:14 -08:00
4ede6351ec addressed some review comments 2020-02-14 12:45:32 -08:00
24bbcb466f wip: fixing add criteria 2020-02-13 16:13:29 -08:00
682601477c Add glyphs (#2667)
- icon-flag for use with VISTA Frame Accountability;
- icon-conditional, and bg-icon-conditional for Conditionals;
2020-02-13 13:11:24 -08:00
b6b5cfe403 fixed default state of output selector 2020-02-13 10:52:37 -08:00
b6ce9c6ed7 trigger label and add divider 2020-02-12 15:58:17 -08:00
6e5e8f0ce8 refactored setOutput 2020-02-12 13:31:55 -08:00
2415d785cc fixed set output field 2020-02-12 12:45:38 -08:00
2b5d6beb84 resolved conflicts 2020-02-12 11:14:17 -08:00
86316d8940 Add multiple criteria, set and persist inputs 2020-02-12 10:41:43 -08:00
1f2b5ec5c8 WIP: separating criterion level logic to its own component 2020-02-11 10:28:59 -08:00
0a9d23d86f Merge pull request #2633 from nasa/pane-persistence
[Navigation Tree] save current state of navigation tree
2020-02-06 16:23:19 -08:00
379e37c881 Merge branch 'master' into pane-persistence 2020-02-06 16:17:59 -08:00
37ef269dce Merge pull request #2611 from nasa/filters-fetch-bug-fix
Check if filters are not equal before refetching
2020-02-06 16:16:12 -08:00
8db6f8f633 criteria component WIP 2020-02-06 15:22:56 -08:00
79557165a3 refactored condition and conditionEdit components into single component named condition 2020-02-06 11:53:31 -08:00
ec1d4abde9 before Deep comments 2020-02-06 07:23:32 -08:00
07c5e2800a Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into condition-clone 2020-02-04 15:19:11 -08:00
79811d6662 Merge condition-ui branch into this. 2020-02-04 15:12:33 -08:00
67919ece16 Merge pull request #2656 from nasa/condition-ui-redo
Condition ui redo
2020-02-04 15:11:02 -08:00
7029dcf09e Merge commit 'e580734c9593ab863f397b52ec9662f045fc7b61' of https://github.com/nasa/openmct into condition-ui-redo 2020-02-04 14:37:39 -08:00
38deef6e72 Merge branch 'master' into filters-fetch-bug-fix 2020-02-03 13:45:26 -08:00
b6220288ac Merge branch 'master' into f#2594 2020-02-03 13:36:29 -08:00
fc03b3a79d updateTemetry failing to return object on line 328 2020-02-02 08:22:23 -08:00
096d6371f1 add condition no errors 2020-02-01 10:07:40 -08:00
e580734c95 Set criteria options (#2630)
* Set criteria options on condition edit

* Persists telemetry options correctly and loads them on mount

* Fixes saving the input value for criteria

* Display active condition's output in read only view

* Destroy classes and unsubscribe when condition set view is destroyed

* Fixes saving the input value for a criteria

* Handle telemetry removal

* Fixes tests

* Addresses comments - change function names, consolidate compute function

* Addresses review comments
- Use camelCase for events (did not change properties as eslint complains)
- Reduce repeated property access by assigning to a variable
- Use descriptive variable name

* Addressing comments - improves input value field visibility logic

* Change variable name to reflect intent
2020-01-31 14:44:28 -08:00
2690156a9d changed kebab-case event names to camelCase 2020-01-31 13:44:07 -08:00
7ac7a40b1b resolve merge conflicts 2020-01-31 13:21:31 -08:00
dc9e572052 Change variable name to reflect intent 2020-01-31 13:10:36 -08:00
b15ebfd492 Addressing comments - improves input value field visibility logic 2020-01-31 13:08:41 -08:00
8baee7a0c9 WIP: debugging 2020-01-31 09:54:37 -08:00
dc85063467 added isDragging prop to prevent other drag actions 2020-01-31 08:08:39 -08:00
be428b326e Merge branch 'condition-ui' of https://github.com/nasa/openmct into condition-ui 2020-01-30 15:12:34 -08:00
dd0e360709 addressed review comments 2020-01-30 15:11:47 -08:00
04da88e3b4 addressed review comments 2020-01-30 14:53:19 -08:00
9bcab02e35 WIP add criteria 2020-01-30 12:02:08 -08:00
1ff4d41b7c Addresses review comments
- Use camelCase for events (did not change properties as eslint complains)
- Reduce repeated property access by assigning to a variable
- Use descriptive variable name
2020-01-30 11:06:25 -08:00
04a5c8f69f resolve merge 2020-01-29 16:23:13 -08:00
8886a94a01 fixed merge conflict 2020-01-29 14:48:22 -08:00
f25eebdf3f WIP - Adding criteria using this.currentCriteria 2020-01-29 14:43:12 -08:00
f9ba46fe85 added button and set up iteratable render of criteria 2020-01-29 12:45:34 -08:00
6f6fb859d6 Merge branch 'condition-ui' of https://github.com/nasa/openmct into set-criteria-options 2020-01-28 16:33:38 -08:00
1e3389b427 Merge branch 'condition-reorder' of https://github.com/nasa/openmct into condition-reorder 2020-01-28 16:32:07 -08:00
c977c64139 added drag image and grab cursor 2020-01-28 16:31:01 -08:00
e419149378 Addresses comments - change function names, consolidate compute function 2020-01-28 16:27:30 -08:00
a5a3e41d21 Merge branch 'condition-ui' of https://github.com/nasa/openmct into condition-clone 2020-01-28 13:03:43 -08:00
ecef8eaf86 Merge branch 'condition-ui' of https://github.com/nasa/openmct into condition-reorder 2020-01-28 12:40:33 -08:00
de03cfbe64 Merge branch 'condition-ui' of https://github.com/nasa/openmct into condition-ui 2020-01-28 12:36:57 -08:00
03a6de55d6 typo in comment 2020-01-28 12:16:18 -08:00
3c5047df5e passed definition prop instead of just name 2020-01-28 12:10:21 -08:00
3cc630d4c2 added testdata toggle control 2020-01-28 11:44:29 -08:00
b3488c54cd Fixes tests 2020-01-28 10:36:13 -08:00
01b1d66bea duplicate condition complete 2020-01-28 10:05:58 -08:00
2e82edb306 remove unused var 2020-01-27 13:44:56 -08:00
8f0e773ac1 remove storing navigated to local storage 2020-01-27 13:43:13 -08:00
223a0feada comments to describe localStorage implementation 2020-01-27 10:17:27 -08:00
bc9cadaa77 fixed reorder bugs 2020-01-27 09:34:50 -08:00
0fd0da8331 rework local storage mechanisms
* use one navigated local storage item instead one per node

* use one expanded local storage item instead of one per node

* fix navigated

* collapse children when node collapsed
2020-01-27 01:06:40 -08:00
f42ec7e2c5 Handle telemetry removal 2020-01-26 23:30:19 -08:00
d6a422fbdb Fixes saving the input value for a criteria 2020-01-26 22:56:21 -08:00
d98b54bea7 Destroy classes and unsubscribe when condition set view is destroyed 2020-01-26 22:21:06 -08:00
0beda1d053 Display active condition's output in read only view 2020-01-26 22:19:27 -08:00
e912ab8f4e Fixes saving the input value for criteria 2020-01-26 22:14:26 -08:00
5055a18ca1 reorder wip 2020-01-25 12:46:15 -08:00
fa21911287 persist expanded and navigated state of node to local storage 2020-01-25 00:52:36 -08:00
96746f4042 drag to reorder conditions 2020-01-24 13:23:02 -08:00
b22ad3ded9 Persists telemetry options correctly and loads them on mount 2020-01-24 11:26:31 -08:00
7e0f475c63 style changes for dnd 2020-01-23 16:13:28 -08:00
efb3c2b71e Merge branch 'condition-ui' of https://github.com/nasa/openmct into set-criteria-options 2020-01-23 15:54:24 -08:00
862ea6986f Set criteria options on condition edit 2020-01-23 15:51:08 -08:00
cfa5dcb02e drag wip 2020-01-23 12:16:16 -08:00
23aaada79d Evaluate telemetry criterion results and display the output of conditions accordingly 2020-01-23 12:08:28 -08:00
9e4458db10 fix merge conflicts 2020-01-23 10:28:59 -08:00
a8da06033c condition view with highlighting of current condition, add/remove condition, change name, output and persist 2020-01-23 10:24:53 -08:00
0bf3597147 Display the current output based on the first condition that has evaluated to true.
Fix remove condition
2020-01-22 21:35:22 -08:00
cfd9730055 Adds missing updated method
Adds definition property to CurrentOutput
2020-01-22 13:45:32 -08:00
e88ead30dc Merge branch 'condition-from-identifier' of https://github.com/nasa/openmct into conditionSet-ui-merge 2020-01-22 13:39:08 -08:00
67b24ce846 Persist conditionCollection to the conditionSet
Populate persisted operation
2020-01-22 13:29:01 -08:00
709c3fff65 no current output rendered 2020-01-22 13:15:44 -08:00
ab6e87ae6b Updates to operation show up in conditionclass 2020-01-22 13:01:23 -08:00
cdb7066bed Moves condition class initialization to conditionEdit component
sets correct condition output in condition edit component
2020-01-22 12:07:14 -08:00
73d0507f1f persist name change 2020-01-22 11:15:49 -08:00
4d263bcf32 added persist for remove 2020-01-22 08:53:25 -08:00
2d8f61172d fixed remove 2020-01-22 08:17:03 -08:00
621c1dc11e renders current output section 2020-01-21 16:00:21 -08:00
dd136a5ff4 WIP: set current condition 2020-01-21 13:37:36 -08:00
8fc785bbd6 Merge branch 'condition-class' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-21 10:18:51 -08:00
82be503f4f Fix broken rendering 2020-01-17 13:00:41 -08:00
7feb933519 Fix errors after converting conditionCollection to persist identifiers only 2020-01-17 11:26:46 -08:00
e806e5a293 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-17 11:04:41 -08:00
770951c6da Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into condition-class 2020-01-17 11:02:27 -08:00
7b7c7b528a 1. Persist the condition domain object — done
2. Persist the condition identifier only in configuration.conditionCollection array, not the domain object — done
3. WIP - Retrieve the condition domain object and instantiate the condition classes on load
2020-01-16 16:11:23 -08:00
a554aa20f8 Addresses code review comments (#2624)
* Addresses code review comments
- Change copyright to 2020
- Fix class parameters, tests

* Fixes this for initilize function
2020-01-16 15:56:58 -08:00
ff1ef1f184 fixes typo 2020-01-16 15:48:40 -08:00
bf1efaf912 Merge branch 'condition-class' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-16 15:47:34 -08:00
ff2bc41317 Merge branch 'telemetry-criterion' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-16 15:47:04 -08:00
bdaf8aff15 Merge branch 'condition-view' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-16 15:46:28 -08:00
1dc4f9f6bb Fixes this for the handleConditionUpdated function 2020-01-16 15:44:59 -08:00
d6320f5da1 Fixes this for initilize function 2020-01-16 15:43:33 -08:00
276be5e857 Merge branch 'condition-class' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-16 15:36:29 -08:00
3101e77ecc Merge branch 'telemetry-criterion' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-16 15:34:53 -08:00
ab6dae16f1 Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into conditionSet-with-classes 2020-01-16 15:32:40 -08:00
36222d79c6 Updates copyright, jsdocs, small refactor 2020-01-16 15:30:49 -08:00
8034317796 Merge branch 'telemetry-criterion' of https://github.com/nasa/openmct into condition-class 2020-01-16 15:08:15 -08:00
a59f3a550e Merge branch 'topic-conditionals' of https://github.com/nasa/openmct into condition-class 2020-01-16 15:07:57 -08:00
415b967c0b Addresses code review comments
- Change copyright to 2020
- Fix class parameters, tests
2020-01-16 15:03:14 -08:00
8df549e8d9 Addresses comments from PR: https://github.com/nasa/openmct/pull/2621 (#2623)
* Addresses comments from PR: https://github.com/nasa/openmct/pull/2621

* Fixes import path.
2020-01-15 15:39:31 -08:00
68949e070c Merge pull request #2621 from nasa/conditionSet-telemetry
Updates conditionSet composition policy to allow only telemetry since…
2020-01-14 11:04:46 -08:00
654333dabe Removes fdescribe 2020-01-14 10:59:11 -08:00
1589e4236a Merge branch 'conditionSet-telemetry' of https://github.com/nasa/openmct into condition-class 2020-01-13 22:51:13 -08:00
7db4ac8ff6 Fixes API link. (#2618) 2020-01-08 12:38:12 -08:00
03829af2ad check if filters are not equal before refetching 2020-01-05 20:56:53 -08:00
322cd94be7 add a check for filters on initialize to prevent refetching with empty filters (#2609) 2019-12-26 11:52:01 -08:00
5d31806fb7 fix #2596 2019-12-22 01:31:23 +01:00
384f0efcb3 by default add new frame to end of container (#2603) 2019-12-19 16:59:41 -08:00
210 changed files with 11674 additions and 3672 deletions

View File

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

2
API.md
View File

@ -231,7 +231,7 @@ attributes
of this object. This is used for specifying an icon to appear next to each
object of this type.
The [Open MCT Tutorials](https://github.com/openmct/openmct-tutorial) provide a
The [Open MCT Tutorials](https://github.com/nasa/openmct-tutorial) provide a
step-by-step examples of writing code for Open MCT that includes a [section on
defining a new object type](https://github.com/nasa/openmct-tutorial#step-3---providing-objects).

View File

@ -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.
-->
-->

View File

@ -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.
*****************************************************************************/
*****************************************************************************/

View File

@ -1,80 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for All files</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>
/
</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Branches</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/0</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>0/0</span>
</div>
</div>
</div>
<div class='status-line high'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div><div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage
generated by <a href="http://istanbul-js.org/" target="_blank">istanbul</a> at Wed Dec 11 2019 13:15:10 GMT-0800 (Pacific Standard Time)
</div>
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
if (typeof prettyPrint === 'function') {
prettyPrint();
}
};
</script>
<script src="sorter.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 B

View File

@ -1,158 +0,0 @@
var addSorting = (function () {
"use strict";
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() { return document.querySelector('.coverage-summary'); }
// returns the thead element of the summary table
function getTableHeader() { return getTable().querySelector('thead tr'); }
// returns the tbody element of the summary table
function getTableBody() { return getTable().querySelector('tbody'); }
// returns the th element for nth column
function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; }
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML = colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function (a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function (a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function () {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i =0 ; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function () {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData(cols);
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);

View File

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

View File

@ -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 || {};

View File

@ -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) {

View File

@ -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
};
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {

View File

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

View File

@ -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 = {

View File

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

View File

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

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

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

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -252,6 +252,7 @@ define([
// Plugin's that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.PlotlyPlot());
this.install(this.plugins.TelemetryTable());
this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin());
@ -265,6 +266,7 @@ define([
this.install(this.plugins.ImportExport());
this.install(this.plugins.WebPage());
this.install(this.plugins.Condition());
this.install(this.plugins.ConditionWidget());
}
MCT.prototype = Object.create(EventEmitter.prototype);

View File

@ -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) : [];
}
/**

View File

@ -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) {

View File

@ -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>

View File

@ -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"

View File

@ -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 () {

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -20,83 +20,132 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import * as EventEmitter from 'eventemitter3';
import EventEmitter from 'EventEmitter';
import uuid from 'uuid';
import TelemetryCriterion from "@/plugins/condition/criterion/TelemetryCriterion";
import { TRIGGER } from "@/plugins/condition/utils/constants";
import TelemetryCriterion from "./criterion/TelemetryCriterion";
import { evaluateResults } from './utils/evaluator';
import { getLatestTimestamp } from './utils/time';
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
/*
* conditionDefinition = {
* trigger: 'any'/'all',
* conditionConfiguration = {
* id: uuid,
* trigger: 'any'/'all'/'not','xor',
* criteria: [
* {
* telemetry: '',
* operation: '',
* input: '',
* metaDataKey: '',
* key: 'someTelemetryObjectKey'
* input: [],
* metadata: ''
* }
* ]
* }
*/
export default class ConditionClass extends EventEmitter {
constructor(conditionDefinition, openmct) {
/**
* 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
*/
constructor(conditionConfiguration, openmct, conditionManager) {
super();
this.openmct = openmct;
this.id = this.openmct.objects.makeKeyString(conditionDefinition.identifier);
if (conditionDefinition.criteria) {
this.createCriteria(conditionDefinition.criteria);
} else {
this.criteria = [];
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 = conditionDefinition.trigger;
this.result = null;
this.trigger = conditionConfiguration.configuration.trigger;
}
updateTrigger(conditionDefinition) {
if (this.trigger !== conditionDefinition.trigger) {
this.trigger = conditionDefinition.trigger;
this.handleConditionUpdated();
getResult(datum) {
if (!datum || !datum.id) {
console.log('no data received');
return;
}
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.getResult(datum, this.conditionManager.telemetryObjects);
} else {
criterion.getResult(datum);
}
});
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
}
generateCriterion(criterionDefinition) {
return {
id: uuid(),
operation: criterionDefinition.operation || '',
input: criterionDefinition.input === undefined ? [] : criterionDefinition.input,
metaDataKey: criterionDefinition.metaDataKey || '',
key: criterionDefinition.key || ''
};
isAnyOrAllTelemetry(criterion) {
return (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any'));
}
createCriteria(criterionDefinitions) {
criterionDefinitions.forEach((criterionDefinition) => {
this.addCriterion(criterionDefinition);
isTelemetryUsed(id) {
return this.criteria.some(criterion => {
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
});
}
updateCriteria(criterionDefinitions) {
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 || '',
telemetryObject: this.conditionManager.telemetryObjects[this.openmct.objects.makeKeyString(criterionConfiguration.telemetry)],
operation: criterionConfiguration.operation || '',
input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input,
metadata: criterionConfiguration.metadata || ''
};
}
createCriteria(criterionConfigurations) {
criterionConfigurations.forEach((criterionConfiguration) => {
this.addCriterion(criterionConfiguration);
});
}
updateCriteria(criterionConfigurations) {
this.destroyCriteria();
this.createCriteria(criterionDefinitions);
this.createCriteria(criterionConfigurations);
}
updateTelemetry() {
this.criteria.forEach((criterion) => {
criterion.updateTelemetry(this.conditionManager.telemetryObjects);
});
}
/**
* adds criterion to the condition.
*/
addCriterion(criterionDefinition) {
let criterionDefinitionWithId = this.generateCriterion(criterionDefinition || null);
let criterion = new TelemetryCriterion(criterionDefinitionWithId, this.openmct);
criterion.on('criterionUpdated', (data) => {
this.handleCriterionUpdated(data);
});
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));
if (!this.criteria) {
this.criteria = [];
}
this.criteria.push(criterion);
this.handleConditionUpdated();
return criterionDefinitionWithId.id;
return criterionConfigurationWithId.id;
}
findCriterion(id) {
@ -114,24 +163,17 @@ export default class ConditionClass extends EventEmitter {
return criterion;
}
updateCriterion(id, criterionDefinition) {
updateCriterion(id, criterionConfiguration) {
let found = this.findCriterion(id);
if (found) {
const newCriterionDefinition = this.generateCriterion(criterionDefinition);
let newCriterion = new TelemetryCriterion(newCriterionDefinition, this.openmct);
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', (result) => {
this.handleCriterionUpdated(id, result);
});
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
this.criteria.splice(found.index, 1, newCriterion);
this.handleConditionUpdated();
}
}
removeCriterion(id) {
if (this.destroyCriterion(id)) {
this.handleConditionUpdated();
}
}
@ -139,31 +181,53 @@ export default class ConditionClass extends EventEmitter {
let found = this.findCriterion(id);
if (found) {
let criterion = found.item;
criterion.unsubscribe();
criterion.off('criterionUpdated', (result) => {
this.handleCriterionUpdated(id, result);
criterion.off('criterionUpdated', (obj) => {
this.handleCriterionUpdated(obj);
});
criterion.destroy();
this.criteria.splice(found.index, 1);
return true;
}
return false;
}
handleCriterionUpdated(criterion) {
// reevaluate the condition's output
// TODO: should we save the result of a criterion here or in the criterion object itself?
let found = this.findCriterion(criterion.id);
if (found) {
this.criteria[found.index] = criterion;
this.handleConditionUpdated();
this.criteria[found.index] = criterion.data;
}
// this.handleConditionUpdated();
}
handleConditionUpdated() {
console.log(this);
// trigger an updated event so that consumers can react accordingly
this.emitResult();
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() {
@ -179,20 +243,7 @@ export default class ConditionClass extends EventEmitter {
return success;
}
//TODO: implement as part of the evaluator class task.
evaluate() {
if (this.trigger === TRIGGER.ANY) {
this.result = false;
} else if (this.trigger === TRIGGER.ALL) {
this.result = false;
}
}
emitResult() {
this.emit('conditionUpdated', {
id: this.id,
trigger: this.trigger,
criteria: this.criteria
});
destroy() {
this.destroyCriteria();
}
}

View File

@ -0,0 +1,372 @@
/*****************************************************************************
* 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)
);
// TODO check if this is needed
this.updateConditionTelemetry();
}
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.removeConditionTelemetry();
}
initialize() {
this.conditionClassCollection = [];
if (this.conditionSetDomainObject.configuration.conditionCollection.length) {
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
this.initCondition(conditionConfiguration, index);
});
}
}
updateConditionTelemetry() {
this.conditionClassCollection.forEach((condition) => condition.updateTelemetry());
}
removeConditionTelemetry() {
let conditionsChanged = false;
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration) => {
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 = '';
conditionsChanged = true;
}
}
});
});
if (conditionsChanged) {
this.persistConditions();
}
}
updateCondition(conditionConfiguration, index) {
let condition = this.conditionClassCollection[index];
condition.update(conditionConfiguration);
this.conditionSetDomainObject.configuration.conditionCollection[index] = conditionConfiguration;
this.persistConditions();
}
initCondition(conditionConfiguration, index) {
let condition = new Condition(conditionConfiguration, this.openmct, this);
if (index !== undefined) {
this.conditionClassCollection.splice(index + 1, 0, condition);
} else {
this.conditionClassCollection.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.conditionClassCollection[index];
condition.destroy();
this.conditionClassCollection.splice(index, 1);
this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1);
this.persistConditions();
}
findConditionById(id) {
return this.conditionClassCollection.find(conditionClass => conditionClass.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.conditionClassCollection.length) {
return Promise.resolve([]);
}
return this.compositionLoad.then(() => {
let latestTimestamp;
let conditionResults = {};
const conditionRequests = this.conditionClassCollection
.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.conditionClassCollection) {
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.conditionClassCollection.forEach(condition => {
condition.getResult(normalizedDatum);
});
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.conditionClassCollection.forEach((condition) => {
condition.destroy();
})
}
}

View 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.conditionClassCollection[0].id;
expect(defaultConditionId).toEqual(mockCondition.id);
});
});

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -23,8 +23,7 @@
export default function ConditionSetCompositionPolicy(openmct) {
return {
allow: function (parent, child) {
let parentType = parent.type;
if (parentType === 'conditionSet' && !openmct.telemetry.isTelemetryObject(child)) {
if (parent.type === 'conditionSet' && !openmct.telemetry.isTelemetryObject(child)) {
return false;
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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,13 +22,12 @@
import ConditionSetCompositionPolicy from './ConditionSetCompositionPolicy';
fdescribe('ConditionSetCompositionPolicy', () => {
describe('ConditionSetCompositionPolicy', () => {
let policy;
let testTelemetryObject;
let openmct = {};
let parentDomainObject;
let composition;
beforeAll(function () {
testTelemetryObject = {
@ -57,7 +56,6 @@ fdescribe('ConditionSetCompositionPolicy', () => {
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject']);
policy = new ConditionSetCompositionPolicy(openmct);
parentDomainObject = {};
composition = {};
});
it('returns true for object types that are not conditionSets', function () {

View 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
}
}
])
};
}
}

View 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];
}
}
}

View 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;

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -23,11 +23,14 @@
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-summary-widget'; // TODO: replace with class for new icon
this.cssClass = 'icon-conditional';
}
canView(domainObject) {
@ -58,7 +61,7 @@ export default class ConditionSetViewProvider {
isEditing
}
},
template: '<condition-set ref="conditionSet" :isEditing="isEditing"></condition-set>'
template: '<condition-set :isEditing="isEditing"></condition-set>'
});
},
onEditModeChange: (isEditing) => {
@ -70,4 +73,12 @@ export default class ConditionSetViewProvider {
}
};
}
priority(domainObject) {
if (domainObject.type === 'conditionSet') {
return Number.MAX_VALUE;
} else {
return DEFAULT_VIEW_PRIORITY;
}
}
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -25,15 +25,22 @@ import {TRIGGER} from "./utils/constants";
import TelemetryCriterion from "./criterion/TelemetryCriterion";
let openmct = {},
mockListener,
testConditionDefinition,
testTelemetryObject,
conditionObj;
conditionObj,
conditionManager,
mockTelemetryReceived,
mockTimeSystems;
describe("The condition", function () {
beforeEach (() => {
mockListener = jasmine.createSpy('listener');
conditionManager = jasmine.createSpyObj('conditionManager',
['on']
);
mockTelemetryReceived = jasmine.createSpy('listener');
conditionManager.on('telemetryReceived', mockTelemetryReceived);
testTelemetryObject = {
identifier:{ namespace: "", key: "test-object"},
type: "test-object",
@ -54,42 +61,57 @@ describe("The condition", function () {
}]
}
};
conditionManager.telemetryObjects = {
"test-object": testTelemetryObject
};
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', 'subscribe']);
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.values);
mockTimeSystems = {
key: 'utc'
};
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
testConditionDefinition = {
trigger: TRIGGER.ANY,
criteria: [
{
operation: 'equalTo',
input: false,
metaDataKey: 'value',
key: testTelemetryObject.identifier
}
]
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
openmct,
conditionManager
);
conditionObj.on('conditionUpdated', mockListener);
});
it("generates criteria with an id", function () {
const testCriterion = testConditionDefinition.criteria[0];
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.metaDataKey).toEqual(testCriterion.metaDataKey);
expect(criterion.key).toEqual(testCriterion.key);
expect(criterion.metadata).toEqual(testCriterion.metadata);
expect(criterion.telemetry).toEqual(testCriterion.telemetry);
});
it("initializes with an id", function () {
@ -100,14 +122,13 @@ describe("The condition", function () {
expect(conditionObj.criteria.length).toEqual(1);
let criterion = conditionObj.criteria[0];
expect(criterion instanceof TelemetryCriterion).toBeTrue();
expect(criterion.operator).toEqual(testConditionDefinition.operator);
expect(criterion.input).toEqual(testConditionDefinition.input);
expect(criterion.metaDataKey).toEqual(testConditionDefinition.metaDataKey);
expect(criterion.key).toEqual(testConditionDefinition.key);
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.trigger);
expect(conditionObj.trigger).toEqual(testConditionDefinition.configuration.trigger);
});
it("destroys all criteria for a condition", function () {

View File

@ -0,0 +1,163 @@
/*****************************************************************************
* 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));
}
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();
}
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();
}
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;
this.isEditing = undefined;
this.callback = undefined;
}
}

View File

@ -1,39 +1,371 @@
/*****************************************************************************
* 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 id="conditionArea"
class="c-cs-ui__conditions"
:class="['widget-condition', { 'widget-condition--current': condition.isCurrent }]"
<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="title-bar">
<span class="condition-name">
{{ condition.name }}
</span>
<span class="condition-output">
Output: {{ condition.output }}
</span>
<div class="c-condition-h__drop-target"></div>
<div v-if="isEditing"
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="!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 class="condition-config">
<span class="condition-description">
{{ condition.description }}
</span>
<div v-else
class="c-condition c-condition--browse"
>
<!-- 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: {
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 {
conditionData: {}
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: 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>

View File

@ -1,132 +1,228 @@
/*****************************************************************************
* 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="c-cs__ui_section"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__ui__header">
<span class="c-cs__ui__header-label">Conditions</span>
<div class="c-cs__header c-section__header">
<span
class="is-enabled flex-elem"
:class="['c-cs__disclosure-triangle', { 'c-cs__disclosure-triangle--expanded': expanded }]"
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__ui_content"
class="c-cs__content"
>
<div v-show="isEditing"
class="help"
class="hint"
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
>
<span>The first condition to match is the one that wins. Drag conditions to rearrange.</span>
<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>
<div class="holder add-condition-button-wrapper align-left">
<button
v-show="isEditing"
id="addCondition"
class="c-cs-button c-cs-button--major add-condition-button"
@click="addCondition"
>
<span class="c-cs-button__label">Add Condition</span>
</button>
</div>
<div class="condition-collection">
<div v-for="condition in conditionCollection"
:key="condition.identifier.key"
class="conditionArea"
>
<div v-if="isEditing">
<ConditionEdit :condition="condition" />
</div>
<div v-else>
<Condition :condition="condition" />
</div>
</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"
: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/components/Condition.vue';
import ConditionEdit from '../../condition/components/ConditionEdit.vue';
import uuid from 'uuid';
import ConditionClass from '../Condition';
import Condition from './Condition.vue';
import ConditionManager from '../ConditionManager';
export default {
inject: ['openmct', 'domainObject'],
components: {
Condition,
ConditionEdit
Condition
},
props: {
isEditing: Boolean
isEditing: Boolean,
testData: {
type: Object,
required: true,
default: () => {
return {
applied: false,
conditionTestInputs: []
}
}
}
},
data() {
return {
expanded: true,
parentKeyString: this.openmct.objects.makeKeyString(this.domainObject.identifier),
conditionCollection: [],
conditions: []
conditionResults: {},
conditions: [],
telemetryObjs: [],
moveIndex: undefined,
isDragging: false,
defaultOutput: undefined,
dragCounter: 0
};
},
watch: {
defaultOutput(newOutput, oldOutput) {
this.$emit('updateDefaultOutput', newOutput);
},
testData: {
handler() {
this.updateTestData();
},
deep: true
}
},
destroyed() {
this.composition.off('add', this.addTelemetry);
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.telemetryObjs = [];
this.instantiate = this.openmct.$injector.get('instantiate');
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addTelemetry);
this.composition.on('add', this.addTelemetryObject);
this.composition.on('remove', this.removeTelemetryObject);
this.composition.load();
this.conditionCollection = this.domainObject.configuration ? this.domainObject.configuration.conditionCollection : [];
if (!this.conditionCollection.length) {this.addCondition(null, true)}
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: {
addTelemetry(telemetryDomainObject) {
this.telemetryObjs.push(telemetryDomainObject);
handleConditionSetResultUpdated(data) {
this.$emit('conditionSetResultUpdated', data)
},
addCondition(event, isDefault) {
let conditionDO = this.getConditionDomainObject(!!isDefault);
this.conditionCollection.unshift(conditionDO);
observeForChanges() {
this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => {
this.conditionCollection = newConditionCollection;
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 = [];
let condition = new ConditionClass(conditionDO, this.openmct);
this.conditions.push(condition);
for (let i = 0; i < oldIndexArr.length; i++) {
reorderPlan.push({oldIndex: Number(newIndexArr[i]), newIndex: i});
}
this.reorder(reorderPlan);
},
getConditionDomainObject(isDefault) {
let conditionObj = {
isDefault: isDefault,
isCurrent: true,
identifier: {
namespace: "",
key: uuid()
},
name: isDefault ? 'Default' : 'Unnamed Condition',
trigger: 'any',
criteria: isDefault ? [] : [{
operation: '',
input: '',
metaDataKey: this.openmct.telemetry.getMetadata(this.telemetryObjs[0]).values()[0].key,
key: this.telemetryObjs.length ? this.openmct.objects.makeKeyString(this.telemetryObjs[0].identifier) : null
}],
output: 'false',
summary: 'summary description'
};
let conditionDOKeyString = this.openmct.objects.makeKeyString(conditionObj.identifier);
let newDO = this.instantiate(conditionObj, conditionDOKeyString);
return newDO.useCapability('adapter');
dragComplete() {
this.isDragging = false;
},
updateCondition(updatedCondition) {
let index = _.findIndex(this.conditions, (condition) => condition.id === updatedCondition.id);
this.conditions[index] = updatedCondition;
addTelemetryObject(domainObject) {
this.telemetryObjs.push(domainObject);
this.$emit('telemetryUpdated', this.telemetryObjs);
},
removeCondition(identifier) {
let index = _.findIndex(this.conditionCollection, (condition) => this.openmct.objects.makeKeyString(identifier) === condition.identifier.key);
this.conditionCollection.splice(index, 1);
removeTelemetryObject(identifier) {
let index = _.findIndex(this.telemetryObjs, (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) {
let oldConditions = this.conditionCollection.slice();
reorderPlan.forEach((reorderEvent) => {
this.$set(this.conditionCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]);
});
this.conditionManager.reorderConditions(reorderPlan);
},
cloneCondition(data) {
this.conditionManager.cloneCondition(data.condition, data.index);
},
updateTestData() {
this.conditionManager.updateTestData(this.testData);
}
}
}

View File

@ -0,0 +1,154 @@
/*****************************************************************************
* 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-for="(criterionDescription, index) in criterionDescriptions"
:key="criterionDescription"
class="c-style__condition-desc__text"
>
<template v-if="!index">When</template>
{{ criterionDescription }}
<template v-if="index < (criterionDescriptions.length-1)">{{ triggerDescription }}</template>
</span>
</div>
</template>
<script>
import { TRIGGER } from "@/plugins/condition/utils/constants";
import { OPERATIONS } from "@/plugins/condition/utils/operations";
export default {
name: 'ConditionDescription',
inject: [
'openmct'
],
props: {
showLabel: {
type: Boolean,
default: false
},
condition: {
type: Object,
default() {
return undefined;
}
}
},
data() {
return {
criterionDescriptions: [],
triggerDescription: ''
}
},
watch: {
condition: {
handler(val) {
this.getConditionDescription();
},
deep: true
}
},
mounted() {
this.getConditionDescription();
},
methods: {
getTriggerDescription(trigger) {
let description = '';
switch(trigger) {
case TRIGGER.ANY:
case TRIGGER.XOR:
description = 'or';
break;
case TRIGGER.ALL:
case TRIGGER.NOT: description = 'and';
break;
}
return description;
},
getConditionDescription() {
if (this.condition) {
this.triggerDescription = this.getTriggerDescription(this.condition.configuration.trigger);
this.criterionDescriptions = [];
this.condition.configuration.criteria.forEach((criterion, index) => {
this.getCriterionDescription(criterion, index);
});
if (this.condition.isDefault) {
this.criterionDescriptions.splice(0, 0, 'all else fails');
}
} else {
this.criterionDescriptions = [];
}
},
getCriterionDescription(criterion, index) {
if (!criterion.telemetry) {
let description = `Unknown ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else if (criterion.telemetry === 'all' || criterion.telemetry === 'any') {
const telemetryDescription = criterion.telemetry === 'all' ? 'All telemetry' : 'Any telemetry';
let description = `${telemetryDescription} ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else {
this.openmct.objects.get(criterion.telemetry).then((telemetryObject) => {
if (telemetryObject.type === 'unknown') {
let description = `Unknown ${criterion.metadata} ${this.getOperatorText(criterion.operation, criterion.input)}`;
this.criterionDescriptions.splice(index, 0, description);
} else {
let metadataValue = criterion.metadata;
let inputValue = criterion.input;
if (criterion.metadata) {
this.telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
const metadataObj = this.telemetryMetadata.valueMetadatas.find((metadata) => metadata.key === criterion.metadata);
if (metadataObj) {
if (metadataObj.name) {
metadataValue = metadataObj.name;
}
if(metadataObj.enumerations && inputValue.length) {
if (metadataObj.enumerations[inputValue[0]] && metadataObj.enumerations[inputValue[0]].string) {
inputValue = [metadataObj.enumerations[inputValue[0]].string];
}
}
}
}
let description = `${telemetryObject.name} ${metadataValue} ${this.getOperatorText(criterion.operation, inputValue)}`;
if (this.criterionDescriptions[index]) {
this.criterionDescriptions[index] = description;
} else {
this.criterionDescriptions.splice(index, 0, description);
}
}
});
}
},
getOperatorText(operationName, values) {
const found = OPERATIONS.find((operation) => operation.name === operationName);
return found ? found.getDescription(values) : '';
}
}
}
</script>

View File

@ -1,233 +0,0 @@
<template>
<div class="c-cs-editui__conditions"
:class="['widget-condition', { 'widget-condition--current': condition.isCurrent }]"
>
<div class="title-bar">
<span
class="c-c__menu-hamburger"
:class="{ 'is-enabled': !condition.isDefault }"
@click="expanded = !condition.expanded"
></span>
<span
class="is-enabled flex-elem"
:class="['c-c__disclosure-triangle', { 'c-c__disclosure-triangle--expanded': expanded }]"
@click="expanded = !condition.expanded"
></span>
<div class="condition-summary">
<span class="condition-name">{{ condition.name }}</span>
<span class="condition-description">{{ condition.description }}</span>
</div>
<span v-if="!condition.isDefault"
class="is-enabled c-c__duplicate"
></span>
<span v-if="!condition.isDefault"
class="is-enabled c-c__trash"
@click="removeCondition"
></span>
</div>
<div v-if="expanded"
class="condition-config-edit widget-rule-content c-sw-editui__rules-wrapper holder widget-rules-wrapper flex-elem expanded"
>
<div id="ruleArea"
class="c-sw-editui__rules widget-rules"
>
<div class="c-sw-rule">
<div class="c-sw-rule__ui l-compact-form l-widget-rule has-local-controls">
<div>
<ul class="t-widget-rule-config">
<li>
<label>Condition Name</label>
<span class="controls">
<input v-model="condition.name"
class="t-rule-name-input"
type="text"
>
</span>
</li>
<li>
<label>Output</label>
<span class="controls">
<select ref="outputSelect"
@change="getOutputBinary">
<option value="false">False</option>
<option value="true">True</option>
<option value="string">String</option>
</select>
<input v-if="stringOutputField"
ref="outputString"
class="t-rule-name-input"
type="text"
:value="condition.output"
@keyup="getOutputString"
>
</span>
</li>
</ul>
<div v-if="!condition.isDefault"
class="widget-rule-content expanded"
>
<ul class="t-widget-rule-config">
<li class="has-local-controls t-condition">
<label>Match when</label>
<span class="controls">
<select>
<option value="all">all criteria are met</option>
<option value="any">any criteria are met</option>
</select>
</span>
</li>
</ul>
<ul class="t-widget-rule-config">
<li v-if="telemetryObject && telemetryMetadata"
class="has-local-controls t-condition"
>
<label>when</label>
<span class="t-configuration">
<span class="controls">
<select class="">
<option value="">- Select Telemetry -</option>
<option :value="telemetryObject.key">{{ telemetryObject.name }}</option>
</select>
</span>
<span class="controls">
<select v-model="selectedMetaDataKey">
<option value="">- Select Field -</option>
<option v-for="option in telemetryMetadata"
:key="option.key"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
<span class="controls">
<select @change="getOperationKey">
<option value="">- Select Comparison -</option>
<option v-for="option in operations"
:key="option.name"
:value="option.name"
>
{{ option.text }}
</option>
</select>
<input v-if="comparisonValueField"
class="t-rule-name-input"
type="text"
@keyup="getOperationValue"
>
</span>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { OPERATIONS } from '../utils/operations';
export default {
inject: ['openmct', 'domainObject'],
props: {
condition: {
type: Object,
required: true
}
},
data() {
let conditionCollection = this.domainObject.configuration.conditionCollection;
return {
expanded: true,
name: this.condition.name,
description: this.condition.description,
conditionCollection,
telemetryObject: this.telemetryObject,
telemetryMetadata: this.telemetryMetadata,
operations: OPERATIONS,
selectedMetaDataKey: null,
selectedOperationKey: null,
stringOutputField: false,
comparisonValueField: false
};
},
mounted() {
if (this.condition.output !== 'false' && this.condition.output !== 'true') {
this.$refs.outputSelect.value = 'string';
this.stringOutputField = true;
}
this.updateTelemetry();
},
updated() {
this.updateCurrentCondition();
this.persist();
},
methods: {
removeCondition(ev) {
const conditionDiv = ev.target.closest('.conditionArea');
const conditionCollectionDiv = conditionDiv.closest('.condition-collection');
const index = Array.from(conditionCollectionDiv.children).indexOf(conditionDiv);
this.domainObject.configuration.conditionCollection.splice(index, 1);
},
updateTelemetry() {
if (this.hasTelemetry()) {
this.openmct.objects.get(this.condition.criteria[0].key).then((obj) => {
this.telemetryObject = obj;
this.telemetryMetadata = this.openmct.telemetry.getMetadata(this.telemetryObject).values();
this.selectedMetaDataKey = this.telemetryMetadata[0].key;
console.log('ConditionEdit', this.telemetryObject, this.telemetryMetadata);
});
} else {
this.telemetryObject = null;
}
},
hasTelemetry() {
return this.condition.criteria.length && this.condition.criteria[0].key;
},
persist(index) {
if (index) {
this.openmct.objects.mutate(this.domainObject, `configuration.conditionCollection[${index}]`, this.domainObject.configuration.conditionCollection[index]);
} else {
this.openmct.objects.mutate(this.domainObject, 'configuration.conditionCollection', this.domainObject.configuration.conditionCollection);
}
},
updateCurrentCondition() {
// TODO: replace based on telemetry
if (this.conditionCollection.length > 1) {
this.conditionCollection.forEach((condition, index) => {
index === 0 ? condition.isCurrent = true : condition.isCurrent = false
});
} else {
this.conditionCollection[0].isCurrent = true;
}
},
getOutputBinary(ev) {
if (ev.target.value !== 'string') {
this.condition.output = ev.target.value;
this.stringOutputField = false;
} else {
this.stringOutputField = true;
}
},
getOutputString(ev) {
this.condition.output = ev.target.value;
},
getOperationKey(ev) {
console.log(ev.target.value)
if (ev.target.value !== 'isUndefined' && ev.target.value !== 'isDefined') {
this.comparisonValueField = true;
} else {
this.comparisonValueField = false;
}
this.selectedOperationKey = ev.target.value;
},
getOperationValue(ev) {
this.selectedOperationKey = ev.target.value;
}
}
}
</script>

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* 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 v-if="conditionErrors.length"
class="c-condition__errors"
>
<div v-for="(error, index) in conditionErrors"
:key="index"
class="u-alert u-alert--block u-alert--with-icon"
>{{ error.message.errorText }} {{ error.additionalInfo }}
</div>
</div>
</template>
<script>
import { ERROR } from "@/plugins/condition/utils/constants";
export default {
name: 'ConditionError',
inject: [
'openmct'
],
props: {
condition: {
type: Object,
default() {
return undefined;
}
}
},
data() {
return {
conditionErrors: []
}
},
mounted() {
this.getConditionErrors();
},
methods: {
getConditionErrors() {
if (this.condition) {
this.condition.configuration.criteria.forEach((criterion, index) => {
this.getCriterionErrors(criterion, index);
});
}
},
getCriterionErrors(criterion, index) {
if (!criterion.telemetry) {
this.conditionErrors.push({
message: ERROR.TELEMETRY_NOT_FOUND,
additionalInfo: ''
});
} else {
if (criterion.telemetry !== 'all' && criterion.telemetry !== 'any') {
this.openmct.objects.get(criterion.telemetry).then((telemetryObject) => {
if (telemetryObject.type === 'unknown') {
this.conditionErrors.push({
message: ERROR.TELEMETRY_NOT_FOUND,
additionalInfo: criterion.telemetry ? `Key: ${this.openmct.objects.makeKeyString(criterion.telemetry)}` : ''
});
}
});
}
}
}
}
}
</script>

View File

@ -1,26 +1,65 @@
/*****************************************************************************
* 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-object-view u-contents">
<div class="c-cs-edit w-condition-set">
<div class="c-sw-edit__ui holder">
<CurrentOutput :condition-collection="domainObject.conditionCollection" />
<TestData :is-editing="isEditing" />
<ConditionCollection :is-editing="isEditing"
:condition-collection="domainObject.conditionCollection"
/>
<div class="c-cs">
<section class="c-cs__current-output c-section">
<div class="c-cs__content c-cs__current-output-value">
<span class="c-cs__current-output-value__label">Current Output</span>
<span class="c-cs__current-output-value__value">
<template v-if="currentConditionOutput">
{{ currentConditionOutput }}
</template>
<template v-else>
{{ defaultConditionOutput }}
</template>
</span>
</div>
</section>
<div class="c-cs__test-data-and-conditions-w">
<TestData class="c-cs__test-data"
:is-editing="isEditing"
:test-data="testData"
:telemetry="telemetryObjs"
@updateTestData="updateTestData"
/>
<ConditionCollection class="c-cs__conditions"
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@telemetryUpdated="updateTelemetry"
/>
</div>
</div>
</template>
<script>
import CurrentOutput from './CurrentOutput.vue';
import TestData from './TestData.vue';
import ConditionCollection from './ConditionCollection.vue';
export default {
inject: ["openmct", "domainObject"],
components: {
CurrentOutput,
TestData,
ConditionCollection
},
@ -28,10 +67,32 @@ export default {
isEditing: Boolean
},
data() {
let conditionCollection = this.domainObject.configuration.conditionCollection;
return {
conditionCollection
currentConditionOutput: '',
defaultConditionOutput: '',
telemetryObjs: [],
testData: {}
}
},
mounted() {
this.conditionSetIdentifier = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.testData = {
applied: false,
conditionTestInputs: this.domainObject.configuration.conditionTestData || []
};
},
methods: {
updateCurrentOutput(currentConditionResult) {
this.currentConditionOutput = currentConditionResult.output;
},
updateDefaultOutput(output) {
this.currentConditionOutput = output;
},
updateTelemetry(telemetryObjs) {
this.telemetryObjs = telemetryObjs;
},
updateTestData(testData) {
this.testData = testData;
}
}
};

View File

@ -0,0 +1,295 @@
/*****************************************************************************
* 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="u-contents">
<div class="c-cdef__separator c-row-separator"></div>
<span class="c-cdef__label">{{ setRowLabel }}</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select ref="telemetrySelect"
v-model="criterion.telemetry"
@change="updateMetadataOptions"
>
<option value="">- Select Telemetry -</option>
<option value="all">all telemetry</option>
<option value="any">any telemetry</option>
<option v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span v-if="criterion.telemetry"
class="c-cdef__control"
>
<select ref="metadataSelect"
v-model="criterion.metadata"
@change="updateOperations"
>
<option value="">- Select Field -</option>
<option v-for="option in telemetryMetadataOptions"
:key="option.key"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
<span v-if="criterion.telemetry && criterion.metadata"
class="c-cdef__control"
>
<select v-model="criterion.operation"
@change="updateInputVisibilityAndValues"
>
<option value="">- Select Comparison -</option>
<option v-for="option in filteredOps"
:key="option.name"
:value="option.name"
>
{{ option.text }}
</option>
</select>
<template v-if="!enumerations.length">
<span v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
>
<input v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@blur="persist"
>
<span v-if="inputIndex < inputCount-1">and</span>
</span>
</template>
<span v-else>
<span v-if="inputCount && criterion.operation"
class="c-cdef__control"
>
<select v-model="criterion.input[0]"
@change="persist"
>
<option v-for="option in enumerations"
:key="option.string"
:value="option.value.toString()"
>
{{ option.string }}
</option>
</select>
</span>
</span>
</span>
</span>
</div>
</template>
<script>
import { OPERATIONS } from '../utils/operations';
import { INPUT_TYPES } from '../utils/operations';
export default {
inject: ['openmct'],
props: {
criterion: {
type: Object,
required: true
},
telemetry: {
type: Array,
required: true,
default: () => []
},
index: {
type: Number,
required: true
},
trigger: {
type: String,
required: true
}
},
data() {
return {
telemetryMetadataOptions: [],
operations: OPERATIONS,
inputCount: 0,
rowLabel: '',
operationFormat: '',
enumerations: [],
inputTypes: INPUT_TYPES
}
},
computed: {
setRowLabel: function () {
let operator = this.trigger === 'all' ? 'and ': 'or ';
return (this.index !== 0 ? operator : '') + 'when';
},
filteredOps: function () {
return this.operations.filter(op => op.appliesTo.indexOf(this.operationFormat) !== -1);
},
setInputType: function () {
let type = '';
for (let i = 0; i < this.filteredOps.length; i++) {
if (this.criterion.operation === this.filteredOps[i].name) {
if (this.filteredOps[i].appliesTo.length) {
type = this.inputTypes[this.filteredOps[i].appliesTo[0]];
} else {
type = 'text'
}
break;
}
}
return type;
}
},
watch: {
telemetry: {
handler(newTelemetry, oldTelemetry) {
this.checkTelemetry();
},
deep: true
}
},
mounted() {
this.updateMetadataOptions();
},
methods: {
checkTelemetry() {
if(this.criterion.telemetry) {
if (this.criterion.telemetry === 'any' || this.criterion.telemetry === 'all') {
this.updateMetadataOptions();
} else {
if (!this.telemetry.find((telemetryObj) => this.openmct.objects.areIdsEqual(this.criterion.telemetry, telemetryObj.identifier))) {
//telemetry being used was removed. So reset this criterion.
this.criterion.telemetry = '';
this.criterion.metadata = '';
this.criterion.input = [];
this.criterion.operation = '';
this.persist();
}
}
}
},
updateOperationFormat() {
this.enumerations = [];
let foundMetadata = this.telemetryMetadataOptions.find((value) => {
return value.key === this.criterion.metadata;
});
if (foundMetadata) {
if (foundMetadata.enumerations !== undefined) {
this.operationFormat = 'enum';
this.enumerations = foundMetadata.enumerations;
} else if (foundMetadata.format === 'string' || foundMetadata.format === 'number') {
this.operationFormat = foundMetadata.format;
} else if (foundMetadata.hints.hasOwnProperty('range')) {
this.operationFormat = 'number';
} else if (foundMetadata.hints.hasOwnProperty('domain')) {
this.operationFormat = 'number';
} else if (foundMetadata.key === 'name') {
this.operationFormat = 'string';
} else {
this.operationFormat = 'number';
}
}
this.updateInputVisibilityAndValues();
},
updateMetadataOptions(ev) {
if (ev) {
this.clearDependentFields(ev.target);
this.persist();
}
if (this.criterion.telemetry) {
const telemetry = (this.criterion.telemetry === 'all' || this.criterion.telemetry === 'any') ? this.telemetry : [{
identifier: this.criterion.telemetry
}];
let telemetryPromises = telemetry.map((telemetryObject) => this.openmct.objects.get(telemetryObject.identifier));
Promise.all(telemetryPromises).then(telemetryObjects => {
this.telemetryMetadataOptions = [];
telemetryObjects.forEach(telemetryObject => {
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.addMetaDataOptions(telemetryMetadata.values());
});
this.updateOperations();
});
}
},
addMetaDataOptions(options) {
if (!this.telemetryMetadataOptions) {
this.telemetryMetadataOptions = options;
}
options.forEach((option) => {
const found = this.telemetryMetadataOptions.find((metadataOption) => {
return (metadataOption.key && (metadataOption.key === option.key)) && (metadataOption.name && (metadataOption.name === option.name))
});
if (!found) {
this.telemetryMetadataOptions.push(option);
}
});
},
updateOperations(ev) {
this.updateOperationFormat();
if (ev) {
this.clearDependentFields(ev.target);
this.persist();
}
},
updateInputVisibilityAndValues(ev) {
if (ev) {
this.clearDependentFields();
this.persist();
}
for (let i = 0; i < this.filteredOps.length; i++) {
if (this.criterion.operation === this.filteredOps[i].name) {
this.inputCount = this.filteredOps[i].inputCount;
}
}
if (!this.inputCount) {
this.criterion.input = [];
}
},
clearDependentFields(el) {
if (el === this.$refs.telemetrySelect) {
this.criterion.metadata = '';
} else if (el === this.$refs.metadataSelect) {
if (!this.filteredOps.find(operation => operation.name === this.criterion.operation)) {
this.criterion.operation = '';
this.criterion.input = this.enumerations.length ? [this.enumerations[0].value.toString()] : [];
this.inputCount = 0;
}
} else {
if (this.enumerations.length && !this.criterion.input.length) {
this.criterion.input = [this.enumerations[0].value.toString()];
}
this.inputCount = 0;
}
},
persist() {
this.$emit('persist', this.criterion);
}
}
};
</script>

View File

@ -1,52 +0,0 @@
<template>
<section id="current-output">
<div class="c-cs__ui__header">
<span class="c-cs__ui__header-label">Current Output</span>
<span
class="is-enabled flex-elem"
:class="['c-cs__disclosure-triangle', { 'c-cs__disclosure-triangle--expanded': expanded }]"
@click="expanded = !expanded"
></span>
</div>
<div v-if="expanded"
class="c-cs__ui_content"
>
<div>
<span class="current-output">{{ conditionCollection[currentConditionIndex].output }}</span>
</div>
</div>
</section>
</template>
<script>
export default {
inject: ['openmct', 'domainObject'],
props: {
isEditing: Boolean
},
data() {
let conditionCollection = this.domainObject.configuration.conditionCollection;
let currentConditionIndex = 0;
return {
expanded: true,
conditionCollection,
currentConditionIndex
}
},
mounted() {
this.currentConditionIndex = this.getCurrentConditionIndex();
},
methods: {
getCurrentConditionIndex() {
let currentConditionIndex;
this.conditionCollection.forEach((condition, index) => {
if (condition.isCurrent) {
currentConditionIndex = index;
}
});
return currentConditionIndex;
}
}
}
</script>

View File

@ -1,51 +1,120 @@
/*****************************************************************************
* 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 v-show="isEditing"
id="test-data"
class="test-data"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__ui__header">
<span class="c-cs__ui__header-label">Test Data</span>
<div class="c-cs__header c-section__header">
<span
class="is-enabled flex-elem"
:class="['c-cs__disclosure-triangle', { 'c-cs__disclosure-triangle--expanded': expanded }]"
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">Test Data</div>
</div>
<div v-if="expanded"
class="c-cs__ui_content"
class="c-cs__content"
>
<label class="checkbox custom">
<input type="checkbox"
class="t-test-data-checkbox"
>
<span>Apply Test Data</span>
</label>
<div class="t-test-data-config">
<div class="c-cs-editui__conditions widget-condition">
<form>
<label>
<span>Set</span>
<select>
<option>- Select Input -</option>
</select>
</label>
<span class="is-enabled flex-elem c-cs__duplicate"></span>
<span class="is-enabled flex-elem c-cs__trash"></span>
</form>
</div>
<div class="c-cs-editui__conditions widget-condition">
<form>
<label>
<span>Set</span>
<select>
<option>- Select Input -</option>
</select>
</label>
<span class="is-enabled c-cs__duplicate"></span>
<span class="is-enabled c-cs__trash"></span>
</form>
</div>
<div class="c-cs__test-data__controls c-cdef__controls"
:disabled="!telemetry.length"
>
<label class="c-toggle-switch">
<input
type="checkbox"
:checked="isApplied"
@change="applyTestData"
>
<span class="c-toggle-switch__slider"></span>
<span class="c-toggle-switch__label">Apply Test Data</span>
</label>
</div>
<div class="c-cs-tests">
<span v-for="(testInput, tIndex) in testInputs"
:key="tIndex"
class="c-test-datum c-cs-test"
>
<span class="c-cs-test__label">Set</span>
<span class="c-cs-test__controls">
<span class="c-cdef__control">
<select v-model="testInput.telemetry"
@change="updateMetadata(testInput)"
>
<option value="">- Select Telemetry -</option>
<option v-for="(telemetryOption, index) in telemetry"
:key="index"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span v-if="testInput.telemetry"
class="c-cdef__control"
>
<select v-model="testInput.metadata"
@change="updateTestData"
>
<option value="">- Select Field -</option>
<option v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
:key="index"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
<span v-if="testInput.metadata"
class="c-cdef__control__inputs"
>
<input v-model="testInput.value"
placeholder="Enter test input"
type="text"
class="c-cdef__control__input"
@change="updateTestData"
>
</span>
</span>
<div class="c-cs-test__buttons">
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test datum"
@click="addTestInput(testInput)"
></button>
<button class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test datum"
@click="removeTestInput(tIndex)"
></button>
</div>
</span>
</div>
<button
v-show="isEditing"
id="addTestDatum"
class="c-button c-button--major icon-plus labeled"
@click="addTestInput"
>
<span class="c-cs-button__label">Add Test Datum</span>
</button>
</div>
</section>
</template>
@ -54,15 +123,114 @@
export default {
inject: ['openmct'],
props: {
isEditing: Boolean
isEditing: Boolean,
telemetry: {
type: Array,
required: true,
default: () => []
},
testData: {
type: Object,
required: true,
default: () => {
return {
applied: false,
conditionTestInputs: []
}
}
}
},
data() {
return {
expanded: true
expanded: true,
isApplied: false,
testInputs: [],
telemetryMetadataOptions: {}
};
},
watch: {
isEditing(editing) {
if (!editing) {
this.resetApplied();
}
},
telemetry: {
handler() {
this.initializeMetadata();
},
deep: true
},
testData: {
handler() {
this.initialize();
},
deep: true
}
},
beforeDestroy() {
this.resetApplied();
},
mounted() {
this.initialize();
this.initializeMetadata();
},
methods: {
applyTestData() {
this.isApplied = !this.isApplied;
this.updateTestData();
},
initialize() {
if (this.testData && this.testData.conditionTestInputs) {
this.testInputs = this.testData.conditionTestInputs;
}
if (!this.testInputs.length) {
this.addTestInput();
}
},
initializeMetadata() {
this.telemetry.forEach((telemetryObject) => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
});
},
addTestInput(testInput) {
this.testInputs.push(Object.assign({
telemetry: '',
metadata: '',
input: ''
}, testInput));
},
removeTestInput(index) {
this.testInputs.splice(index, 1);
this.updateTestData();
},
getId(identifier) {
if (identifier) {
return this.openmct.objects.makeKeyString(identifier);
}
return [];
},
updateMetadata(testInput) {
if (testInput.telemetry) {
const id = this.openmct.objects.makeKeyString(testInput.telemetry);
if(this.telemetryMetadataOptions[id]) {
return;
}
let telemetryMetadata = this.openmct.telemetry.getMetadata(testInput);
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
}
},
resetApplied() {
this.isApplied = false;
this.updateTestData();
},
updateTestData() {
this.$emit('updateTestData', {
applied: this.isApplied,
conditionTestInputs: this.testInputs
});
}
}
}
</script>

View File

@ -1,142 +0,0 @@
.c-cs-edit {
padding: 0;
}
section {
padding-bottom: 5px;
}
.c-cs__ui__header {
background-color: #D0D1D3;
padding: 0.4em 0.6em;
text-transform: uppercase;
font-size: 0.8em;
font-weight: bold;
color: #7C7D80;
display: flex;
justify-content: stretch;
align-items: center;
}
.c-cs__ui__header-label {
display: inline-block;
width: 100%;
}
.c-cs__ui_content {
padding: 0.4em;
}
.c-cs__ui_content .help {
font-style: italic;
padding: 0.4em 0;
}
.current-output {
text-transform: uppercase;
font-weight: bold;
margin: 0.4em 0.6em;
}
.condition-output {
color: #999;
}
.condition-description {
color: #333;
}
.checkbox.custom {
display: flex;
align-items: center;
margin-left: 0.6em;
}
.checkbox.custom span {
display: inline-block;
margin-left: 0.6em;
}
.t-test-data-config {
margin-top: 5px;
}
.widget-condition form {
padding: 0.5em;
display: flex;
align-items: center;
justify-content: stretch;
}
.widget-condition form label {
flex-grow: 1;
margin-left: 0;
}
.widget-condition form input {
min-height: 24px;
}
.c-cs-button[class*="--major"],
.c-cs-button[class*='is-active'],
.c-cs-button--menu[class*="--major"],
.c-cs-button--menu[class*='is-active'] {
border: solid 1.5px #0B427C;
background-color: #4778A3;
padding: 0.2em 0.6em;
margin: 0.4em;
font-weight: bold;
color: #eee;
border-radius: 6px;
}
.c-cs__disclosure-triangle,
.c-cs__menu-hamburger,
.c-cs__duplicate,
.c-cs__trash {
$d: 8px;
color: $colorDisclosureCtrl;
width: $d;
visibility: hidden;
&.is-enabled {
cursor: pointer;
visibility: visible;
&:hover {
color: $colorDisclosureCtrlHov;
}
&:before {
$s: .5;
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
}
}
}
.c-cs__disclosure-triangle {
&:before {
content: $glyph-icon-arrow-right;
}
&--expanded {
&:before {
transform: rotate(90deg);
}
}
}
.c-cs__duplicate {
margin-right: 0.3em;
&:before {
content: $glyph-icon-duplicate;
}
}
.c-cs__trash {
&:before {
content: $glyph-icon-trash;
}
}

View File

@ -1,154 +0,0 @@
.widget-condition {
background-color: #eee;
margin: 0 0 0.6em;
border-radius: 3px;
&--current {
background-color: #DEECFA;
}
}
.title-bar {
display: flex;
align-items: center;
justify-content: stretch;
padding: 0.4em 0;
}
.title-bar span {
margin-right: 0.6em;
}
.title-bar span.c-c__duplicate,
.title-bar span.c-c__trash{
margin-right: 0;
margin-left: 0.3em;
}
.widget-condition > div {
margin: 0 0.4em;
}
.condition-name {
font-weight: bold;
}
.condition-summary .condition-description {
color: #999;
}
.condition-summary {
flex-grow: 1;
}
.condition-config {
padding: 0.3em 0;
}
.widget-condition form label {
flex-grow: 1;
margin-left: 0;
}
.l-widget-rule {
padding: 0;
}
.l-compact-form ul li {
padding: 0;
}
.widget-rule-content.expanded {
margin: 0 3px;
}
.widget-rule-content.expanded ul {
border-top: solid 1px #ccc;
padding: 2px;
}
.l-compact-form ul li .controls {
display: inline-flex;
flex-grow: inherit;
padding: 2px 0;
}
.l-compact-form ul li > label {
display: block;
width: 90px;
min-width: 90px;
text-align: right;
line-height: 20px;
}
.l-compact-form ul li .controls input[type="text"],
.l-compact-form ul li .controls input[type="search"],
.l-compact-form ul li .controls input[type="number"],
.l-compact-form ul li .controls button,
.l-compact-form ul li .controls select {
min-height: 20px;
}
.condition-config-edit {
padding: 3px 0;
}
.c-c__disclosure-triangle,
.c-c__menu-hamburger,
.c-c__duplicate,
.c-c__trash {
$d: 8px;
color: $colorDisclosureCtrl;
width: $d;
visibility: hidden;
&.is-enabled {
cursor: pointer;
visibility: visible;
&:hover {
color: $colorDisclosureCtrlHov;
}
&:before {
$s: .5;
display: block;
font-family: symbolsfont;
font-size: 1rem * $s;
}
}
}
.c-c__disclosure-triangle {
&:before {
content: $glyph-icon-arrow-right;
}
&--expanded {
&:before {
transform: rotate(90deg);
}
}
}
.c-c__menu-hamburger {
&:before {
content: $glyph-icon-menu-hamburger;
}
}
.c-c__duplicate {
&:before {
content: $glyph-icon-duplicate;
}
}
.c-c__trash {
&:before {
content: $glyph-icon-trash;
}
}

View File

@ -0,0 +1,311 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/***************************** DRAGGING */
.is-active-dragging {
.c-condition-h__drop-target {
height: 3px;
margin-bottom: $interiorMarginSm;
}
}
.c-condition-h {
&__drop-target {
border-radius: $controlCr;
height: 0;
min-height: 0;
transition: background-color, height;
transition-duration: 150ms;
}
&.is-drag-target {
.c-condition > * {
pointer-events: none; // Keeps the JS drop handler from being intercepted by internal elements
}
.c-condition-h__drop-target {
background-color: rgba($colorKey, 0.7);
}
}
}
.c-cs {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
/************************** CONDITION SET LAYOUT */
&__current-output {
flex: 0 0 auto;
}
&__test-data-and-conditions-w {
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 100%;
overflow: hidden;
}
&__test-data,
&__conditions {
flex: 0 0 auto;
overflow: hidden;
}
&__test-data {
flex: 0 0 auto;
max-height: 50%;
&.is-expanded {
margin-bottom: $interiorMargin * 4;
}
}
&__conditions {
flex: 1 1 auto;
> * + * {
margin-top: $interiorMarginSm;
}
}
&__content {
display: flex;
flex-direction: column;
flex: 0 1 auto;
overflow: hidden;
> * {
flex: 0 0 auto;
overflow: hidden;
+ * {
margin-top: $interiorMarginSm;
}
}
.c-button {
align-self: start;
}
}
.is-editing & {
// Add some space to kick away from blue editing border indication
padding: $interiorMargin;
}
section {
display: flex;
flex-direction: column;
overflow: hidden;
}
&__conditions-h {
display: flex;
flex-direction: column;
flex: 1 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
.hint {
padding: $interiorMarginSm;
}
/************************** SPECIFIC ITEMS */
&__current-output-value {
flex-direction: row;
align-items: baseline;
padding: 0 $interiorMargin $interiorMarginLg $interiorMargin;
> * {
padding: $interiorMargin 0; // Must do this to align label and value
}
&__label {
color: $colorInspectorSectionHeaderFg;
opacity: 0.9;
text-transform: uppercase;
}
&__value {
$p: $interiorMargin * 3;
font-size: 1.25em;
margin-left: $interiorMargin;
padding-left: $p;
padding-right: $p;
background: rgba(black, 0.2);
border-radius: 5px;
}
}
}
/***************************** CONDITIONS AND TEST DATUM ELEMENTS */
.c-condition,
.c-test-datum {
@include discreteItem();
display: flex;
padding: $interiorMargin;
line-height: 170%; // Aligns text with controls like selects
}
.c-cdef,
.c-cs-test {
&__controls {
display: flex;
flex: 1 1 auto;
flex-wrap: wrap;
> * > * {
margin-right: $interiorMarginSm;
}
}
&__buttons {
white-space: nowrap;
}
}
.c-condition {
flex-direction: column;
min-width: 400px;
> * + * {
margin-top: $interiorMarginSm;
}
&--browse {
.c-condition__summary {
border-top: 1px solid $colorInteriorBorder;
padding-top: $interiorMargin;
}
}
/***************************** HEADER */
&__header {
$h: 22px;
display: flex;
align-items: start;
align-content: stretch;
overflow: hidden;
min-height: $h;
line-height: $h;
> * {
flex: 0 0 auto;
+ * {
margin-left: $interiorMarginSm;
}
}
}
&__drag-grippy {
transform: translateY(50%);
}
&__name {
font-weight: bold;
align-self: baseline; // Fixes bold line-height offset problem
}
&__output,
&__summary {
flex: 1 1 auto;
}
}
/***************************** CONDITION DEFINITION, EDITING */
.c-cdef {
display: grid;
grid-row-gap: $interiorMarginSm;
grid-column-gap: $interiorMargin;
grid-auto-columns: min-content 1fr max-content;
align-items: start;
min-width: 150px;
margin-left: 29px;
overflow: hidden;
&__criteria,
&__match-and-criteria {
display: contents;
}
&__label {
grid-column: 1;
text-align: right;
white-space: nowrap;
}
&__separator {
grid-column: 1 / span 3;
}
&__controls {
align-items: flex-start;
grid-column: 2;
> * > * {
margin-right: $interiorMarginSm;
}
}
&__buttons {
grid-column: 3;
}
}
.c-c__drag-ghost {
width: 100%;
min-height: $interiorMarginSm;
&.dragging {
min-height: 5em;
background-color: lightblue;
border-radius: 2px;
}
}
/***************************** TEST DATA */
.c-cs__test-data {
&__controls {
flex: 0 0 auto;
}
}
.c-cs-tests {
flex: 0 1 auto;
overflow: auto;
padding-right: $interiorMarginSm;
> * + * {
margin-top: $interiorMarginSm;
}
}
.c-cs-test {
> * + * {
margin-left: $interiorMargin;
}
}

View File

@ -0,0 +1,185 @@
/*****************************************************************************
* 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>
<li class="c-tree__item-h">
<div
class="c-tree__item"
:class="{ 'is-alias': isAlias, 'is-navigated-object': navigated }"
@click="handleItemSelected(node.object, node)"
>
<view-control
v-model="expanded"
class="c-tree__item__view-control"
:enabled="hasChildren"
:propagate="false"
/>
<div class="c-tree__item__label c-object-label">
<div
class="c-tree__item__type-icon c-object-label__type-icon"
:class="typeClass"
></div>
<div class="c-tree__item__name c-object-label__name">{{ node.object.name }}</div>
</div>
</div>
<ul
v-if="expanded"
class="c-tree"
>
<li
v-if="isLoading && !loaded"
class="c-tree__item-h"
>
<div class="c-tree__item loading">
<span class="c-tree__item__label">Loading...</span>
</div>
</li>
<condition-set-dialog-tree-item
v-for="child in children"
:key="child.id"
:node="child"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelected"
/>
</ul>
</li>
</template>
<script>
import viewControl from '@/ui/components/viewControl.vue';
export default {
name: 'ConditionSetDialogTreeItem',
inject: ['openmct'],
components: {
viewControl
},
props: {
node: {
type: Object,
required: true
},
selectedItem: {
type: Object,
default() {
return undefined;
}
},
handleItemSelected: {
type: Function,
default() {
return (item) => {};
}
}
},
data() {
return {
hasChildren: false,
isLoading: false,
loaded: false,
children: [],
expanded: false
}
},
computed: {
navigated() {
const itemId = this.selectedItem && this.selectedItem.itemId;
const isSelectedObject = itemId && this.openmct.objects.areIdsEqual(this.node.object.identifier, itemId);
if (isSelectedObject && this.node.objectPath && this.node.objectPath.length > 1) {
const isParent = this.openmct.objects.areIdsEqual(this.node.objectPath[1].identifier, this.selectedItem.parentId);
return isSelectedObject && isParent;
}
return isSelectedObject;
},
isAlias() {
let parent = this.node.objectPath[1];
if (!parent) {
return false;
}
let parentKeyString = this.openmct.objects.makeKeyString(parent.identifier);
return parentKeyString !== this.node.object.location;
},
typeClass() {
let type = this.openmct.types.get(this.node.object.type);
if (!type) {
return 'icon-object-unknown';
}
return type.definition.cssClass;
}
},
watch: {
expanded() {
if (!this.hasChildren) {
return;
}
if (!this.loaded && !this.isLoading) {
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addChild);
this.composition.on('remove', this.removeChild);
this.composition.load().then(this.finishLoading);
this.isLoading = true;
}
}
},
mounted() {
this.domainObject = this.node.object;
let removeListener = this.openmct.objects.observe(this.domainObject, '*', (newObject) => {
this.domainObject = newObject;
});
this.$once('hook:destroyed', removeListener);
if (this.openmct.composition.get(this.node.object)) {
this.hasChildren = true;
}
},
beforeDestroy() {
this.expanded = false;
},
destroyed() {
if (this.composition) {
this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild);
delete this.composition;
}
},
methods: {
addChild(child) {
this.children.push({
id: this.openmct.objects.makeKeyString(child.identifier),
object: child,
objectPath: [child].concat(this.node.objectPath),
navigateToParent: this.navigateToPath
});
},
removeChild(identifier) {
let removeId = this.openmct.objects.makeKeyString(identifier);
this.children = this.children
.filter(c => c.id !== removeId);
},
finishLoading() {
this.isLoading = false;
this.loaded = true;
}
}
}
</script>

View File

@ -0,0 +1,172 @@
/*****************************************************************************
* 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="u-contents">
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">Select Condition Set</div>
</div>
<div class="c-overlay__contents-main c-selector c-tree-and-search">
<div class="c-tree-and-search__search">
<search ref="shell-search"
class="c-search"
:value="searchValue"
@input="searchTree"
@clear="searchTree"
/>
</div>
<!-- loading -->
<div v-if="isLoading"
class="c-tree-and-search__loading loading"
></div>
<!-- end loading -->
<div v-if="(allTreeItems.length === 0) || (searchValue && filteredTreeItems.length === 0)"
class="c-tree-and-search__no-results"
>
No results found
</div>
<!-- main tree -->
<ul v-if="!isLoading"
v-show="!searchValue"
class="c-tree-and-search__tree c-tree"
>
<condition-set-dialog-tree-item
v-for="treeItem in allTreeItems"
:key="treeItem.id"
:node="treeItem"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelection"
/>
</ul>
<!-- end main tree -->
<!-- search tree -->
<ul v-if="searchValue"
class="c-tree-and-search__tree c-tree"
>
<condition-set-dialog-tree-item
v-for="treeItem in filteredTreeItems"
:key="treeItem.id"
:node="treeItem"
:selected-item="selectedItem"
:handle-item-selected="handleItemSelection"
/>
</ul>
<!-- end search tree -->
</div>
</div>
</template>
<script>
import search from '@/ui/components/search.vue';
import ConditionSetDialogTreeItem from './ConditionSetDialogTreeItem.vue';
export default {
inject: ['openmct'],
name: 'ConditionSetSelectorDialog',
components: {
search,
ConditionSetDialogTreeItem
},
data() {
return {
expanded: false,
searchValue: '',
allTreeItems: [],
filteredTreeItems: [],
isLoading: false,
selectedItem: undefined
}
},
mounted() {
this.searchService = this.openmct.$injector.get('searchService');
this.getAllChildren();
},
methods: {
getAllChildren() {
this.isLoading = true;
this.openmct.objects.get('ROOT')
.then(root => {
return this.openmct.composition.get(root).load()
})
.then(children => {
this.isLoading = false;
this.allTreeItems = children.map(c => {
return {
id: this.openmct.objects.makeKeyString(c.identifier),
object: c,
objectPath: [c],
navigateToParent: '/browse'
};
});
});
},
getFilteredChildren() {
this.searchService.query(this.searchValue).then(children => {
this.filteredTreeItems = children.hits.map(child => {
let context = child.object.getCapability('context'),
object = child.object.useCapability('adapter'),
objectPath = [],
navigateToParent;
if (context) {
objectPath = context.getPath().slice(1)
.map(oldObject => oldObject.useCapability('adapter'))
.reverse();
navigateToParent = '/browse/' + objectPath.slice(1)
.map((parent) => this.openmct.objects.makeKeyString(parent.identifier))
.join('/');
}
return {
id: this.openmct.objects.makeKeyString(object.identifier),
object,
objectPath,
navigateToParent
}
});
});
},
searchTree(value) {
this.searchValue = value;
if (this.searchValue !== '') {
this.getFilteredChildren();
}
},
handleItemSelection(item, node) {
if (item && item.type === 'conditionSet') {
const parentId = (node.objectPath && node.objectPath.length > 1) ? node.objectPath[1].identifier : undefined;
this.selectedItem = {
itemId: item.identifier,
parentId
};
this.$emit('conditionSetSelected', item);
}
}
}
}
</script>

View File

@ -0,0 +1,457 @@
/*****************************************************************************
* 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-inspector__styles c-inspect-styles">
<template v-if="!conditionSetDomainObject">
<div class="c-inspect-styles__header">
Object Style
</div>
<div class="c-inspect-styles__content">
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
@persist="updateStaticStyle"
/>
</div>
<button
id="addConditionSet"
class="c-button c-button--major c-toggle-styling-button labeled"
@click="addConditionSet"
>
<span class="c-cs-button__label">Use Conditional Styling...</span>
</button>
</div>
</template>
<template v-else>
<div class="c-inspect-styles__header">
Conditional Object Styles
</div>
<div class="c-inspect-styles__content c-inspect-styles__condition-set">
<a v-if="conditionSetDomainObject"
class="c-object-label icon-conditional"
:href="navigateToPath"
@click="navigateOrPreview"
>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
</a>
<template v-if="isEditing">
<button
id="changeConditionSet"
class="c-button labeled"
@click="addConditionSet"
>
<span class="c-button__label">Change...</span>
</button>
<button class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
></button>
</template>
</div>
<div v-if="conditionsLoaded"
class="c-inspect-styles__conditions"
>
<div v-for="(conditionStyle, index) in conditionalStyles"
:key="index"
class="c-inspect-styles__condition"
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
>
<condition-error :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:is-editing="isEditing"
@persist="updateConditionalStyle"
/>
</div>
</div>
</template>
</div>
</template>
<script>
import StyleEditor from "./StyleEditor.vue";
import ConditionSetSelectorDialog from "./ConditionSetSelectorDialog.vue";
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import Vue from 'vue';
import PreviewAction from "@/ui/preview/PreviewAction.js";
import {getApplicableStylesForItem} from "@/plugins/condition/utils/styleUtils";
export default {
name: 'ConditionalStylesView',
components: {
ConditionDescription,
ConditionError,
StyleEditor
},
inject: [
'openmct',
'selection'
],
data() {
return {
conditionalStyles: [],
staticStyle: undefined,
conditionSetDomainObject: undefined,
isEditing: this.openmct.editor.isEditing(),
conditions: undefined,
conditionsLoaded: false,
navigateToPath: '',
selectedConditionId: ''
}
},
destroyed() {
this.removeListeners();
},
mounted() {
this.itemId = '';
this.getDomainObjectFromSelection();
this.previewAction = new PreviewAction(this.openmct);
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
let objectStyles = this.itemId ? this.domainObject.configuration.objectStyles[this.itemId] : this.domainObject.configuration.objectStyles;
this.initializeStaticStyle(objectStyles);
if (objectStyles && objectStyles.conditionSetIdentifier) {
this.openmct.objects.get(objectStyles.conditionSetIdentifier).then(this.initialize);
this.conditionalStyles = objectStyles.styles;
}
} else {
this.initializeStaticStyle();
}
this.openmct.editor.on('isEditing', this.setEditState);
},
methods: {
isItemType(type, item) {
return item && (item.type === type);
},
getDomainObjectFromSelection() {
let layoutItem;
let domainObject;
if (this.selection[0].length > 1) {
//If there are more than 1 items in the this.selection[0] list, the first one could either be a sub domain object OR a layout drawing control.
//The second item in the this.selection[0] list is the container object (usually a layout)
layoutItem = this.selection[0][0].context.layoutItem;
const item = this.selection[0][0].context.item;
this.canHide = true;
if (item &&
(!layoutItem || (this.isItemType('subobject-view', layoutItem)))) {
domainObject = item;
} else {
domainObject = this.selection[0][1].context.item;
if (layoutItem) {
this.itemId = layoutItem.id;
}
}
} else {
domainObject = this.selection[0][0].context.item;
}
this.domainObject = domainObject;
this.initialStyles = getApplicableStylesForItem(domainObject, layoutItem);
this.$nextTick(() => {
this.removeListeners();
if (this.domainObject) {
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
}
});
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
initialize(conditionSetDomainObject) {
//If there are new conditions in the conditionSet we need to set those styles to default
this.conditionSetDomainObject = conditionSetDomainObject;
this.enableConditionSetNav();
this.initializeConditionalStyles();
},
setEditState(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
} else {
this.subscribeToConditionSet();
}
},
addConditionSet() {
let conditionSetDomainObject;
const handleItemSelection = (item) => {
if (item) {
conditionSetDomainObject = item;
}
};
const dismissDialog = (overlay, initialize) => {
overlay.dismiss();
if (initialize && conditionSetDomainObject) {
this.conditionSetDomainObject = conditionSetDomainObject;
this.conditionalStyles = [];
this.initializeConditionalStyles();
}
};
let vm = new Vue({
provide: {
openmct: this.openmct
},
components: {ConditionSetSelectorDialog},
data() {
return {
handleItemSelection
}
},
template: '<condition-set-selector-dialog @conditionSetSelected="handleItemSelection"></condition-set-selector-dialog>'
}).$mount();
let overlay = this.openmct.overlays.overlay({
element: vm.$el,
size: 'small',
buttons: [
{
label: 'OK',
emphasis: 'true',
callback: () => dismissDialog(overlay, true)
},
{
label: 'Cancel',
callback: () => dismissDialog(overlay, false)
}
],
onDestroy: () => vm.$destroy()
});
},
enableConditionSetNav() {
this.openmct.objects.getOriginalPath(this.conditionSetDomainObject.identifier).then(
(objectPath) => {
this.objectPath = objectPath;
this.navigateToPath = '#/browse/' + this.objectPath
.map(o => o && this.openmct.objects.makeKeyString(o.identifier))
.reverse()
.join('/');
}
);
},
navigateOrPreview(event) {
// If editing, display condition set in Preview overlay; otherwise nav to it while browsing
if (this.openmct.editor.isEditing()) {
event.preventDefault();
this.previewAction.invoke(this.objectPath);
}
},
removeConditionSet() {
this.conditionSetDomainObject = undefined;
this.conditionalStyles = [];
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.itemId) {
domainObjectStyles[this.itemId].conditionSetIdentifier = undefined;
domainObjectStyles[this.itemId].selectedConditionId = undefined;
domainObjectStyles[this.itemId].defaultConditionId = undefined;
delete domainObjectStyles[this.itemId].conditionSetIdentifier;
domainObjectStyles[this.itemId].styles = undefined;
delete domainObjectStyles[this.itemId].styles;
if (_.isEmpty(domainObjectStyles[this.itemId])) {
delete domainObjectStyles[this.itemId];
}
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
domainObjectStyles.selectedConditionId = undefined;
domainObjectStyles.defaultConditionId = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
if (_.isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(domainObjectStyles);
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
updateDomainObjectItemStyles(newItems) {
//check that all items that have been styles still exist. Otherwise delete those styles
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
let itemsToRemove = [];
let keys = Object.keys(domainObjectStyles);
//TODO: Need an easier way to find which properties are itemIds
keys.forEach((key) => {
const keyIsItemId = (key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'defaultConditionId') &&
(key !== 'selectedConditionId') &&
(key !== 'conditionSetIdentifier');
if (keyIsItemId) {
if (!(newItems.find(item => item.id === key))) {
itemsToRemove.push(key);
}
}
});
if (itemsToRemove.length) {
this.removeItemStyles(itemsToRemove, domainObjectStyles);
}
},
removeItemStyles(itemIds, domainObjectStyles) {
itemIds.forEach(itemId => {
if (domainObjectStyles[itemId]) {
domainObjectStyles[itemId] = undefined;
delete domainObjectStyles[this.itemId];
}
});
if (_.isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(domainObjectStyles);
},
initializeConditionalStyles() {
if (!this.conditions) {
this.conditions = {};
}
let conditionalStyles = [];
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
if (conditionConfiguration.isDefault) {
this.selectedConditionId = conditionConfiguration.id;
}
this.conditions[conditionConfiguration.id] = conditionConfiguration;
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
if (foundStyle) {
foundStyle.style = Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles, foundStyle.style);
conditionalStyles.push(foundStyle);
} else {
conditionalStyles.splice(index, 0, {
conditionId: conditionConfiguration.id,
style: Object.assign((this.canHide ? { isStyleInvisible: '' } : {}), this.initialStyles)
});
}
});
//we're doing this so that we remove styles for any conditions that have been removed from the condition set
this.conditionalStyles = conditionalStyles;
this.conditionsLoaded = true;
this.persist(this.getDomainObjectConditionalStyle(this.selectedConditionId));
if (!this.isEditing) {
this.subscribeToConditionSet();
}
},
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject)
.then(output => {
if (output && output.length) {
this.handleConditionSetResultUpdated(output[0]);
}
});
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(this.conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
}
},
handleConditionSetResultUpdated(resultData) {
this.selectedConditionId = resultData ? resultData.conditionId : '';
},
initializeStaticStyle(objectStyles) {
let staticStyle = objectStyles && objectStyles.staticStyle;
if (staticStyle) {
this.staticStyle = {
style: Object.assign({}, this.initialStyles, staticStyle.style)
};
} else {
this.staticStyle = {
style: Object.assign({}, this.initialStyles)
};
}
},
findStyleByConditionId(id) {
return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
},
updateStaticStyle(staticStyle) {
this.staticStyle = staticStyle;
this.persist(this.getDomainObjectConditionalStyle());
},
updateConditionalStyle(conditionStyle) {
let found = this.findStyleByConditionId(conditionStyle.conditionId);
if (found) {
found.style = conditionStyle.style;
this.selectedConditionId = found.conditionId;
this.persist(this.getDomainObjectConditionalStyle());
}
},
getDomainObjectConditionalStyle(defaultConditionId) {
let objectStyle = {
styles: this.conditionalStyles,
staticStyle: this.staticStyle,
selectedConditionId: this.selectedConditionId
};
if (defaultConditionId) {
objectStyle.defaultConditionId = defaultConditionId;
}
if (this.conditionSetDomainObject) {
objectStyle.conditionSetIdentifier = this.conditionSetDomainObject.identifier;
}
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.itemId) {
domainObjectStyles[this.itemId] = objectStyle;
} else {
//we're deconstructing here to ensure that if an item within a domainObject already had a style we don't lose it
domainObjectStyles = {
...domainObjectStyles,
...objectStyle
}
}
return domainObjectStyles;
},
getCondition(id) {
return this.conditions ? this.conditions[id] : {};
},
applySelectedConditionStyle(conditionId) {
this.selectedConditionId = conditionId;
this.persist(this.getDomainObjectConditionalStyle());
},
persist(style) {
this.openmct.objects.mutate(this.domainObject, 'configuration.objectStyles', style);
}
}
}
</script>

View File

@ -0,0 +1,269 @@
/*****************************************************************************
* 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-inspector__styles c-inspect-styles">
<div class="c-inspect-styles__header">
Object Style
</div>
<div class="c-inspect-styles__content">
<div v-if="isStaticAndConditionalStyles"
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
>
Your selection includes one or more items that use Conditional Styling. Applying a static style below will replace any Conditional Styling with the new choice.
</div>
<div v-if="staticStyle"
class="c-inspect-styles__style"
>
<style-editor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="isEditing"
:mixed-styles="mixedStyles"
@persist="updateStaticStyle"
/>
</div>
</div>
</div>
</template>
<script>
import StyleEditor from "./StyleEditor.vue";
import PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionalStyleForItem } from "@/plugins/condition/utils/styleUtils";
export default {
name: 'MultiSelectStylesView',
components: {
StyleEditor
},
inject: [
'openmct',
'selection'
],
data() {
return {
staticStyle: undefined,
isEditing: this.openmct.editor.isEditing(),
mixedStyles: [],
isStaticAndConditionalStyles: false
}
},
destroyed() {
this.removeListeners();
},
mounted() {
this.items = [];
this.previewAction = new PreviewAction(this.openmct);
this.getObjectsAndItemsFromSelection();
this.initializeStaticStyle();
this.openmct.editor.on('isEditing', this.setEditState);
},
methods: {
isItemType(type, item) {
return item && (item.type === type);
},
hasConditionalStyles(domainObject, id) {
return getConditionalStyleForItem(domainObject, id) !== undefined;
},
getObjectsAndItemsFromSelection() {
let domainObject;
let subObjects = [];
//multiple selection
let itemInitialStyles = [];
let itemStyle;
this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem;
if (item && this.isItemType('subobject-view', layoutItem)) {
subObjects.push(item);
itemStyle = getApplicableStylesForItem(item);
if (!this.isStaticAndConditionalStyles) {
this.isStaticAndConditionalStyles = this.hasConditionalStyles(item);
}
} else {
domainObject = selectionItem[1].context.item;
itemStyle = getApplicableStylesForItem(domainObject, layoutItem || item);
this.items.push({
id: layoutItem.id,
applicableStyles: itemStyle
});
if (!this.isStaticAndConditionalStyles) {
this.isStaticAndConditionalStyles = this.hasConditionalStyles(domainObject, layoutItem.id);
}
}
itemInitialStyles.push(itemStyle);
});
const {styles, mixedStyles} = getConsolidatedStyleValues(itemInitialStyles);
this.initialStyles = styles;
this.mixedStyles = mixedStyles;
this.domainObject = domainObject;
this.removeListeners();
if (this.domainObject) {
this.stopObserving = this.openmct.objects.observe(this.domainObject, '*', newDomainObject => this.domainObject = newDomainObject);
this.stopObservingItems = this.openmct.objects.observe(this.domainObject, 'configuration.items', this.updateDomainObjectItemStyles);
}
subObjects.forEach(this.registerListener);
},
updateDomainObjectItemStyles(newItems) {
//check that all items that have been styles still exist. Otherwise delete those styles
let keys = Object.keys(this.domainObject.configuration.objectStyles || {});
keys.forEach((key) => {
if ((key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'conditionSetIdentifier')) {
if (!(newItems.find(item => item.id === key))) {
this.removeItemStyles(key);
}
}
});
},
registerListener(domainObject) {
let id = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.domainObjectsById) {
this.domainObjectsById = {};
}
if (!this.domainObjectsById[id]) {
this.domainObjectsById[id] = domainObject;
this.observeObject(domainObject, id);
}
},
observeObject(domainObject, id) {
let unobserveObject = this.openmct.objects.observe(domainObject, '*', function (newObject) {
this.domainObjectsById[id] = JSON.parse(JSON.stringify(newObject));
}.bind(this));
this.unObserveObjects.push(unobserveObject);
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.unObserveObjects) {
this.unObserveObjects.forEach((unObserveObject) => {
unObserveObject();
});
}
this.unObserveObjects = [];
},
removeItemStyles(itemId) {
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (itemId && domainObjectStyles[itemId]) {
domainObjectStyles[itemId] = undefined;
delete domainObjectStyles[this.itemId];
if (_.isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined;
}
this.persist(this.domainObject, domainObjectStyles);
}
},
removeConditionalStyles(domainObjectStyles, itemId) {
if (itemId) {
domainObjectStyles[itemId].conditionSetIdentifier = undefined;
delete domainObjectStyles[itemId].conditionSetIdentifier;
domainObjectStyles[itemId].styles = undefined;
delete domainObjectStyles[itemId].styles;
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
},
setEditState(isEditing) {
this.isEditing = isEditing;
},
initializeStaticStyle() {
this.staticStyle = {
style: Object.assign({}, this.initialStyles)
};
},
updateStaticStyle(staticStyle, property) {
//update the static style for each of the layoutItems as well as each sub object item
this.staticStyle = staticStyle;
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, this.items));
if (this.domainObjectsById) {
const keys = Object.keys(this.domainObjectsById);
keys.forEach(key => {
let domainObject = this.domainObjectsById[key];
this.persist(domainObject, this.getDomainObjectStyle(domainObject, property));
});
}
this.isStaticAndConditionalStyles = false;
let foundIndex = this.mixedStyles.indexOf(property);
if (foundIndex > -1) {
this.mixedStyles.splice(foundIndex, 1);
}
},
getDomainObjectStyle(domainObject, property, items) {
let domainObjectStyles = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
if (items) {
items.forEach(item => {
let itemStaticStyle = {};
if (domainObjectStyles[item.id] && domainObjectStyles[item.id].staticStyle) {
itemStaticStyle = domainObjectStyles[item.id].staticStyle.style;
}
Object.keys(item.applicableStyles).forEach(key => {
if (property === key) {
itemStaticStyle[key] = this.staticStyle.style[key];
}
});
if (this.isStaticAndConditionalStyles) {
this.removeConditionalStyles(domainObjectStyles, item.id);
}
if (_.isEmpty(itemStaticStyle)) {
itemStaticStyle = undefined;
domainObjectStyles[item.id] = undefined;
} else {
domainObjectStyles[item.id] = Object.assign({}, { staticStyle: { style: itemStaticStyle } });
}
});
} else {
if (!domainObjectStyles.staticStyle) {
domainObjectStyles.staticStyle = {
style: {}
}
}
if (this.isStaticAndConditionalStyles) {
this.removeConditionalStyles(domainObjectStyles);
}
domainObjectStyles.staticStyle.style[property] = this.staticStyle.style[property];
}
return domainObjectStyles;
},
persist(domainObject, style) {
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
}
}
}
</script>

View File

@ -0,0 +1,215 @@
/*****************************************************************************
* 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">
<span :class="[
{ 'is-style-invisible': styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
class="c-style-thumb"
>
<span class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
>
ABC
</span>
</span>
<span class="c-toolbar">
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
/>
<toolbar-button v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
/>
<toolbar-toggle-button v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@change="updateStyleValue"
/>
</span>
</div>
</template>
<script>
import ToolbarColorPicker from "@/ui/toolbar/components/toolbar-color-picker.vue";
import ToolbarButton from "@/ui/toolbar/components/toolbar-button.vue";
import ToolbarToggleButton from "@/ui/toolbar/components/toolbar-toggle-button.vue";
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
import {getStylesWithoutNoneValue} from "@/plugins/condition/utils/styleUtils";
export default {
name: 'StyleEditor',
components: {
ToolbarButton,
ToolbarColorPicker,
ToolbarToggleButton
},
inject: [
'openmct'
],
props: {
isEditing: {
type: Boolean
},
mixedStyles: {
type: Array,
default() {
return [];
}
},
styleItem: {
type: Object,
required: true
}
},
computed: {
itemStyle() {
return getStylesWithoutNoneValue(this.styleItem.style);
},
borderColorOption() {
let value = this.styleItem.style.border.replace('1px solid ', '');
return {
icon: 'icon-line-horz',
title: STYLE_CONSTANTS.borderColorTitle,
value: this.normalizeValueForSwatch(value),
property: 'border',
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('border') > -1
}
},
backgroundColorOption() {
let value = this.styleItem.style.backgroundColor;
return {
icon: 'icon-paint-bucket',
title: STYLE_CONSTANTS.backgroundColorTitle,
value: this.normalizeValueForSwatch(value),
property: 'backgroundColor',
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('backgroundColor') > -1
}
},
colorOption() {
let value = this.styleItem.style.color;
return {
icon: 'icon-font',
title: STYLE_CONSTANTS.textColorTitle,
value: this.normalizeValueForSwatch(value),
property: 'color',
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('color') > -1
}
},
imageUrlOption() {
return {
icon: 'icon-image',
title: STYLE_CONSTANTS.imagePropertiesTitle,
dialog: {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg"
}
]
}
]
},
property: 'imageUrl',
formKeys: ['url'],
value: {url: this.styleItem.style.imageUrl},
isEditing: this.isEditing,
nonSpecific: this.mixedStyles.indexOf('imageUrl') > -1
}
},
isStyleInvisibleOption() {
return {
value: this.styleItem.style.isStyleInvisible,
property: 'isStyleInvisible',
isEditing: this.isEditing,
options: [
{
value: '',
icon: 'icon-eye-disabled',
title: STYLE_CONSTANTS.visibilityHidden
},
{
value: STYLE_CONSTANTS.isStyleInvisible,
icon: 'icon-eye-open',
title: STYLE_CONSTANTS.visibilityVisible
}
]
}
}
},
methods: {
hasProperty(property) {
return property !== undefined;
},
normalizeValueForSwatch(value) {
if (value && value.indexOf('__no_value') > -1) {
return value.replace('__no_value', 'transparent');
}
return value;
},
normalizeValueForStyle(value) {
if (value && value === 'transparent') {
return '__no_value';
}
return value;
},
updateStyleValue(value, item) {
value = this.normalizeValueForStyle(value);
if (item.property === 'border') {
value = '1px solid ' + value;
}
if (value && (value.url !== undefined)) {
this.styleItem.style[item.property] = value.url;
} else {
this.styleItem.style[item.property] = value;
}
this.$emit('persist', this.styleItem, item.property);
}
}
}
</script>

View File

@ -0,0 +1,149 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/********************************************* INSPECTOR STYLES TAB */
.c-inspect-styles {
> * + * {
margin-top: $interiorMargin;
}
&__content,
&__conditions,
&__condition {
> * + * {
margin-top: $interiorMargin;
}
}
&__content {
display: flex;
flex-direction: column;
}
&__condition-set {
display: flex;
flex-direction: row;
align-items: center;
.c-object-label {
flex: 1 1 auto;
}
.c-button {
flex: 0 0 auto;
}
}
&__style,
&__condition {
padding: $interiorMargin;
}
&__condition {
@include discreteItem();
border: 1px solid transparent;
pointer-events: none; // Prevent selecting when the object isn't being edited
&.is-current {
$c: $colorBodyFg;
border-color: rgba($c, 0.5);
background: rgba($c, 0.2);
}
.is-editing & {
cursor: pointer;
pointer-events: initial;
transition: $transOut;
&:hover {
background: rgba($colorBodyFg, 0.1);
transition: $transIn;
}
&.is-current {
$c: $editUIColorBg;
border-color: $c;
background: rgba($c, 0.1);
}
}
}
.c-style {
padding: 2px; // Allow a bit of room for thumb box-shadow
&__condition-desc {
@include ellipsize();
}
}
}
.c-inspect-styles__style {
.is-editing & {
border-bottom: 1px solid $colorInteriorBorder;
}
}
.l-shell:not(.is-editing) .c-inspect-styles {
.c-toolbar {
// Disabled-look toolbar when not editing
pointer-events: none;
cursor: inherit;
// Hide control buttons, like image URL
[class*='--image-url'] {
display: none;
}
// Make buttons look disabled by knocking back icon, not swatch element
.c-icon-button {
&:before {
opacity: $controlDisabledOpacity;
}
}
}
}
.c-toggle-styling-button {
display: none;
.is-editing & {
display: block;
align-self: flex-end;
}
}
.is-style-invisible {
display: none !important;
.is-editing & {
display: block !important;
opacity: 0.2;
}
&.c-style-thumb {
display: block !important;
background-color: transparent !important;
border-color: transparent !important;
@include bgCheckerboard($size: 10px, $imp: true);
opacity: 1;
}
}

View File

@ -0,0 +1,166 @@
/*****************************************************************************
* 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 TelemetryCriterion from './TelemetryCriterion';
import { evaluateResults } from "../utils/evaluator";
import { getLatestTimestamp } from '../utils/time';
export default class AllTelemetryCriterion extends TelemetryCriterion {
/**
* Subscribes/Unsubscribes to telemetry and emits the result
* of operations performed on the telemetry data returned and a given input value.
* @constructor
* @param telemetryDomainObjectDefinition {id: uuid, operation: enum, input: Array, metadata: string, key: {domainObject.identifier} }
* @param openmct
*/
constructor(telemetryDomainObjectDefinition, openmct) {
super(telemetryDomainObjectDefinition, openmct);
}
initialize() {
this.telemetryObjects = { ...this.telemetryDomainObjectDefinition.telemetryObjects };
this.telemetryDataCache = {};
}
isValid() {
return (this.telemetry === 'any' || this.telemetry === 'all') && this.metadata && this.operation;
}
updateTelemetry(telemetryObjects) {
this.telemetryObjects = { ...telemetryObjects };
this.removeTelemetryDataCache();
}
removeTelemetryDataCache() {
const telemetryCacheIds = Object.keys(this.telemetryDataCache);
Object.values(this.telemetryObjects).forEach(telemetryObject => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
const foundIndex = telemetryCacheIds.indexOf(id);
if (foundIndex > -1) {
telemetryCacheIds.splice(foundIndex, 1);
}
});
telemetryCacheIds.forEach(id => {
delete (this.telemetryDataCache[id]);
});
}
formatData(data, telemetryObjects) {
if (data) {
this.telemetryDataCache[data.id] = this.computeResult(data);
}
let keys = Object.keys(telemetryObjects);
keys.forEach((key) => {
let telemetryObject = telemetryObjects[key];
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (this.telemetryDataCache[id] === undefined) {
this.telemetryDataCache[id] = false;
}
});
const datum = {
result: evaluateResults(Object.values(this.telemetryDataCache), this.telemetry)
};
if (data) {
this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
datum[timeSystem.key] = data[timeSystem.key]
});
}
return datum;
}
getResult(data, telemetryObjects) {
const validatedData = this.isValid() ? data : {};
if (validatedData) {
this.telemetryDataCache[validatedData.id] = this.computeResult(validatedData);
}
Object.values(telemetryObjects).forEach(telemetryObject => {
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
if (this.telemetryDataCache[id] === undefined) {
this.telemetryDataCache[id] = false;
}
});
this.result = evaluateResults(Object.values(this.telemetryDataCache), this.telemetry);
}
requestLAD(telemetryObjects) {
const options = {
strategy: 'latest',
size: 1
};
if (!this.isValid()) {
return this.formatData({}, telemetryObjects);
}
let keys = Object.keys(Object.assign({}, telemetryObjects));
const telemetryRequests = keys
.map(key => this.openmct.telemetry.request(
telemetryObjects[key],
options
));
let telemetryDataCache = {};
return Promise.all(telemetryRequests)
.then(telemetryRequestsResults => {
let latestTimestamp;
const timeSystems = this.openmct.time.getAllTimeSystems();
const timeSystem = this.openmct.time.timeSystem();
telemetryRequestsResults.forEach((results, index) => {
const latestDatum = results.length ? results[results.length - 1] : {};
const datumId = keys[index];
const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObjects[datumId]);
telemetryDataCache[datumId] = this.computeResult(normalizedDatum);
latestTimestamp = getLatestTimestamp(
latestTimestamp,
normalizedDatum,
timeSystems,
timeSystem
);
});
const datum = {
result: evaluateResults(Object.values(telemetryDataCache), this.telemetry),
...latestTimestamp
};
return {
id: this.id,
data: datum
};
});
}
destroy() {
delete this.telemetryObjects;
delete this.telemetryDataCache;
}
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -20,69 +20,142 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import * as EventEmitter from 'eventemitter3';
import EventEmitter from 'EventEmitter';
import { OPERATIONS } from '../utils/operations';
export default class TelemetryCriterion extends EventEmitter {
/**
* Subscribes/Unsubscribes to telemetry and emits the result
* of operations performed on the telemetry data returned and a given input value.
* @constructor
* @param telemetryDomainObjectDefinition {id: uuid, operation: enum, input: Array, metadata: string, key: {domainObject.identifier} }
* @param openmct
*/
constructor(telemetryDomainObjectDefinition, openmct) {
super();
this.openmct = openmct;
this.telemetryDomainObjectDefinition = telemetryDomainObjectDefinition;
this.id = telemetryDomainObjectDefinition.id;
this.subscription = null;
this.telemetryMetadata = null;
this.telemetryObjectIdAsString = null;
openmct.objects.get(openmct.objects.makeKeyString(telemetryDomainObjectDefinition.key)).then((obj) => {
if (openmct.telemetry.isTelemetryObject(obj)) {
this.telemetryObject = obj;
this.telemetryObjectIdAsString = openmct.objects.makeKeyString(this.telemetryObject.identifier);
this.telemetryMetadata = openmct.telemetry.getMetadata(this.telemetryObject.identifier);
this.emit('criterionUpdated', this);
this.telemetry = telemetryDomainObjectDefinition.telemetry;
this.operation = telemetryDomainObjectDefinition.operation;
this.input = telemetryDomainObjectDefinition.input;
this.metadata = telemetryDomainObjectDefinition.metadata;
this.result = undefined;
this.initialize();
this.emitEvent('criterionUpdated', this);
}
initialize() {
this.telemetryObject = this.telemetryDomainObjectDefinition.telemetryObject;
this.telemetryObjectIdAsString = this.openmct.objects.makeKeyString(this.telemetryDomainObjectDefinition.telemetry);
}
isValid() {
return this.telemetryObject && this.metadata && this.operation;
}
updateTelemetry(telemetryObjects) {
this.telemetryObject = telemetryObjects[this.telemetryObjectIdAsString];
}
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 formatter = this.openmct.telemetry.getValueFormatter(metadatum);
datum[metadatum.key] = formatter.parse(telemetryDatum[metadatum.source]);
return datum;
}, {});
normalizedDatum.id = id;
return normalizedDatum;
}
formatData(data) {
const datum = {
result: this.computeResult(data)
};
if (data) {
this.openmct.time.getAllTimeSystems().forEach(timeSystem => {
datum[timeSystem.key] = data[timeSystem.key]
});
}
return datum;
}
getResult(data) {
const validatedData = this.isValid() ? data : {};
this.result = this.computeResult(validatedData);
}
requestLAD() {
const options = {
strategy: 'latest',
size: 1
};
if (!this.isValid()) {
return {
id: this.id,
data: this.formatData({})
};
}
return this.openmct.telemetry.request(
this.telemetryObject,
options
).then(results => {
const latestDatum = results.length ? results[results.length - 1] : {};
const normalizedDatum = this.createNormalizedDatum(latestDatum, this.telemetryObject);
return {
id: this.id,
data: this.formatData(normalizedDatum)
};
});
}
findOperation(operation) {
for (let i=0, ii=OPERATIONS.length; i < ii; i++) {
if (operation === OPERATIONS[i].name) {
return OPERATIONS[i].operation;
}
});
}
handleSubscription(datum) {
//data is telemetry values, error
//how do I get data here?
this.emitResult(this.normalizeData(datum));
}
//TODO: Revisit this logic
normalizeData(datum) {
return {
[datum.key]: datum[datum.source]
}
return null;
}
emitResult(data, error) {
this.emit('criterionUpdated', {
identifier: this.id,
data: data,
error: error
});
}
/**
* Subscribes to the telemetry object and returns an unsubscribe function
*/
subscribe(openmct) {
this.subscription = openmct.telemetry.subscribe(this.telemetryObject, (datum) => {
this.handleSubscription(datum);
});
}
/**
* Calls an unsubscribe function returned by subscribe() and deletes any initialized data
*/
unsubscribe() {
//unsubscribe from telemetry source
if (typeof this.subscription === 'function') {
this.subscription();
computeResult(data) {
let result = false;
if (data) {
let comparator = this.findOperation(this.operation);
let params = [];
params.push(data[this.metadata]);
if (this.input instanceof Array && this.input.length) {
this.input.forEach(input => params.push(input));
}
if (typeof comparator === 'function') {
result = !!comparator(params);
}
}
delete this.subscription;
this.emit('criterion::Remove', this.telemetryObjectIdAsString);
delete this.telemetryObjectIdAsString;
return result;
}
emitEvent(eventName, data) {
this.emit(eventName, {
id: this.id,
data: data
});
}
destroy() {
delete this.telemetryObject;
delete this.telemetryMetadata;
delete this.telemetryObjectIdAsString;
}
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, United States Government
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -24,6 +24,7 @@ import TelemetryCriterion from "./TelemetryCriterion";
let openmct = {},
mockListener,
testCriterionDefinition,
testTelemetryObject,
telemetryCriterion;
@ -35,36 +36,64 @@ describe("The telemetry criterion", function () {
type: "test-object",
name: "Test Object",
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
valueMetadatas: [{
key: "value",
name: "Value",
hints: {
range: 2
}
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 1
}
key: "testSource",
source: "value",
name: "Test",
format: "string"
}]
}
};
openmct.objects = jasmine.createSpyObj('objects', ['makeKeyString']);
// openmct.objects.get.and.returnValue(testTelemetryObject);
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
openmct.telemetry = jasmine.createSpyObj('telemetry', ["subscribe"]);
// openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata", "getValueFormatter"]);
openmct.telemetry.isTelemetryObject.and.returnValue(true);
openmct.telemetry.subscribe.and.returnValue(function () {});
openmct.telemetry.getValueFormatter.and.returnValue({
parse: function (value) {
return value;
}
});
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
openmct.time = jasmine.createSpyObj('timeAPI',
['timeSystem', 'bounds', 'getAllTimeSystems']
);
openmct.time.timeSystem.and.returnValue({key: 'system'});
openmct.time.bounds.and.returnValue({start: 0, end: 1});
openmct.time.getAllTimeSystems.and.returnValue([{key: 'system'}]);
testCriterionDefinition = {
id: 'test-criterion-id',
telemetry: openmct.objects.makeKeyString(testTelemetryObject.identifier),
operation: 'textContains',
metadata: 'value',
input: ['Hell'],
telemetryObject: testTelemetryObject
};
mockListener = jasmine.createSpy('listener');
telemetryCriterion = new TelemetryCriterion(
testTelemetryObject,
testCriterionDefinition,
openmct
);
telemetryCriterion.on('criterionUpdated', mockListener);
telemetryCriterion.on('criterionResultUpdated', mockListener);
});
@ -72,41 +101,21 @@ describe("The telemetry criterion", function () {
expect(telemetryCriterion.telemetryObjectIdAsString).toEqual(testTelemetryObject.identifier.key);
});
it("subscribes to telemetry providers", function () {
telemetryCriterion.subscribe();
expect(telemetryCriterion.subscription).toBeDefined();
});
it("normalizes telemetry data", function () {
let result = telemetryCriterion.normalizeData({
key: 'some-key',
source: 'testSource',
testSource: 'Hello'
it("returns a result on new data from relevant telemetry providers", function () {
telemetryCriterion.getResult({
value: 'Hello',
utc: 'Hi',
id: testTelemetryObject.identifier.key
});
expect(result).toEqual({
'some-key': 'Hello'
})
});
it("emits update event on new data from telemetry providers", function () {
spyOn(telemetryCriterion, 'emitResult').and.callThrough();
telemetryCriterion.handleSubscription({
key: 'some-key',
source: 'testSource',
testSource: 'Hello'
});
expect(telemetryCriterion.emitResult).toHaveBeenCalled();
expect(mockListener).toHaveBeenCalled();
});
it("un-subscribes from telemetry providers", function () {
telemetryCriterion.subscribe();
expect(telemetryCriterion.subscription).toBeDefined();
telemetryCriterion.unsubscribe();
expect(telemetryCriterion.subscription).toBeUndefined();
expect(telemetryCriterion.telemetryObjectIdAsString).toBeUndefined();
expect(telemetryCriterion.telemetryObject).toBeUndefined();
expect(telemetryCriterion.telemetryMetadata).toBeUndefined();
expect(telemetryCriterion.result).toBeTrue();
});
// it("does not return a result on new data from irrelavant telemetry providers", function () {
// telemetryCriterion.getResult({
// value: 'Hello',
// utc: 'Hi',
// id: '1234'
// });
// expect(telemetryCriterion.result).toBeFalse();
// });
});

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -20,37 +20,48 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ConditionSetViewProvider from './ConditionSetViewProvider.js';
import ConditionSetCompositionPolicy from "@/plugins/condition/ConditionSetCompositionPolicy";
import ConditionSetCompositionPolicy from "./ConditionSetCompositionPolicy";
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider';
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider';
import ConditionSetViewPolicy from './ConditionSetViewPolicy';
import uuid from "uuid";
export default function ConditionPlugin() {
return function install(openmct) {
openmct.types.addType('condition', {
name: 'Condition',
key: 'condition',
description: 'A list of criteria which will be evaluated based on a trigger',
creatable: false,
initialize: function (domainObject) {
domainObject.composition = [];
}
});
openmct.types.addType('conditionSet', {
name: 'Condition Set',
key: 'conditionSet',
description: 'A set of one or more conditions based on user-specified criteria.',
description: 'Monitor and evaluate telemetry values in real-time with a wide variety of criteria. Use to control the styling of many objects in Open MCT.',
creatable: true,
cssClass: 'icon-summary-widget', // TODO: replace with class for new icon
cssClass: 'icon-conditional',
initialize: function (domainObject) {
domainObject.configuration = {
conditionCollection: []
conditionTestData: [],
conditionCollection: [{
isDefault: true,
id: uuid(),
configuration: {
name: 'Default',
output: 'Default',
trigger: 'all',
criteria: []
},
summary: 'Default condition'
}]
};
domainObject.composition = [];
domainObject.telemetry = {};
}
});
openmct.legacyExtension('policies', {
category: 'view',
implementation: ConditionSetViewPolicy
});
openmct.composition.addPolicy(new ConditionSetCompositionPolicy(openmct).allow);
openmct.telemetry.addProvider(new ConditionSetMetadataProvider(openmct));
openmct.telemetry.addProvider(new ConditionSetTelemetryProvider(openmct));
openmct.objectViews.addProvider(new ConditionSetViewProvider(openmct));
}

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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.
*
@ -26,26 +26,14 @@ import ConditionPlugin from "./plugin";
let openmct = createOpenMct();
openmct.install(new ConditionPlugin());
let conditionDefinition;
let conditionSetDefinition;
let mockDomainObject;
let mockDomainObject2;
let mockConditionSetDomainObject;
let element;
let child;
describe('the plugin', function () {
beforeAll((done) => {
conditionDefinition = openmct.types.get('condition').definition;
mockDomainObject = {
identifier: {
key: 'testConditionKey',
namespace: ''
},
type: 'condition'
};
conditionDefinition.initialize(mockDomainObject);
conditionSetDefinition = openmct.types.get('conditionSet').definition;
const appHolder = document.createElement('div');
@ -56,7 +44,7 @@ describe('the plugin', function () {
child = document.createElement('div');
element.appendChild(child);
mockDomainObject2 = {
mockConditionSetDomainObject = {
identifier: {
key: 'testConditionSetKey',
namespace: ''
@ -64,22 +52,12 @@ describe('the plugin', function () {
type: 'conditionSet'
};
conditionSetDefinition.initialize(mockDomainObject2);
conditionSetDefinition.initialize(mockConditionSetDomainObject);
openmct.on('start', done);
openmct.start(appHolder);
});
let mockConditionObject = {
name: 'Condition',
key: 'condition',
creatable: false
};
it('defines a condition object type with the correct key', () => {
expect(conditionDefinition.key).toEqual(mockConditionObject.key);
});
let mockConditionSetObject = {
name: 'Condition Set',
key: 'conditionSet',
@ -90,18 +68,6 @@ describe('the plugin', function () {
expect(conditionSetDefinition.key).toEqual(mockConditionSetObject.key);
});
describe('the condition object', () => {
it('is not creatable', () => {
expect(conditionDefinition.creatable).toEqual(mockConditionObject.creatable);
});
it('initializes with an empty composition list', () => {
expect(mockDomainObject.composition instanceof Array).toBeTrue();
expect(mockDomainObject.composition.length).toEqual(0);
});
});
describe('the conditionSet object', () => {
it('is creatable', () => {
@ -109,14 +75,17 @@ describe('the plugin', function () {
});
it('initializes with an empty composition list', () => {
expect(mockDomainObject2.composition instanceof Array).toBeTrue();
expect(mockDomainObject2.composition.length).toEqual(0);
expect(mockConditionSetDomainObject.composition instanceof Array).toBeTrue();
expect(mockConditionSetDomainObject.composition.length).toEqual(0);
});
it('provides a view', () => {
const testViewObject = {
id:"test-object",
type: "conditionSet"
type: "conditionSet",
configuration: {
conditionCollection: []
}
};
const applicableViews = openmct.objectViews.get(testViewObject);

View File

@ -1,4 +1,54 @@
/*****************************************************************************
* 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 const TRIGGER = {
ANY: 'any',
ALL: 'all'
ALL: 'all',
NOT: 'not',
XOR: 'xor'
};
export const TRIGGER_LABEL = {
'any': 'when any criteria are met',
'all': 'when all criteria are met',
'not': 'when no criteria are met',
'xor': 'when only one criteria is met'
};
export const STYLE_CONSTANTS = {
isStyleInvisible: 'is-style-invisible',
borderColorTitle: 'Set border color',
textColorTitle: 'Set text color',
backgroundColorTitle: 'Set background color',
imagePropertiesTitle: 'Edit image properties',
visibilityHidden: 'Hidden',
visibilityVisible: 'Visible'
};
export const ERROR = {
'TELEMETRY_NOT_FOUND': {
errorText: 'Telemetry not found for criterion'
},
'CONDITION_NOT_FOUND': {
errorText: 'Condition not found'
}
};

View File

@ -1,7 +1,68 @@
export const computeConditionForAny = (args) => {
return false;
};
/*****************************************************************************
* 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 { TRIGGER } from "./constants";
export const evaluateResults = (results, trigger) => {
if (trigger && trigger === TRIGGER.XOR) {
return matchExact(results, 1);
} else if (trigger && trigger === TRIGGER.NOT) {
return matchExact(results, 0);
} else if (trigger && trigger === TRIGGER.ALL) {
return matchAll(results);
} else {
return matchAny(results);
}
}
function matchAll(results) {
for (const result of results) {
if (!result) {
return false;
}
}
return true;
}
function matchAny(results) {
for (const result of results) {
if (result) {
return true;
}
}
export const computeConditionForAll = (args) => {
return false;
};
}
function matchExact(results, target) {
let matches = 0;
for (const result of results) {
if (result) {
matches++;
}
if (matches > target) {
return false;
}
}
return matches === target;
}

View File

@ -0,0 +1,204 @@
/*****************************************************************************
* 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 { evaluateResults } from './evaluator';
import { TRIGGER } from './constants';
describe('evaluate results', () => {
// const allTrue = [true, true, true, true, true];
// const oneTrue = [false, false, false, false, true];
// const multipleTrue = [false, true, false, true, false];
// const noneTrue = [false, false, false, false, false];
// const allTrueWithUndefined = [true, true, true, undefined, true];
// const oneTrueWithUndefined = [undefined, undefined, undefined, undefined, true];
// const multipleTrueWithUndefined = [true, undefined, true, undefined, true];
// const allUndefined = [undefined, undefined, undefined, undefined, undefined];
// const singleTrue = [true];
// const singleFalse = [false];
// const singleUndefined = [undefined];
// const empty = [];
const tests = [
{
name: 'allTrue',
values: [true, true, true, true, true],
any: true,
all: true,
not: false,
xor: false
}, {
name: 'oneTrue',
values: [false, false, false, false, true],
any: true,
all: false,
not: false,
xor: true
}, {
name: 'multipleTrue',
values: [false, true, false, true, false],
any: true,
all: false,
not: false,
xor: false
}, {
name: 'noneTrue',
values: [false, false, false, false, false],
any: false,
all: false,
not: true,
xor: false
}, {
name: 'allTrueWithUndefined',
values: [true, true, true, undefined, true],
any: true,
all: false,
not: false,
xor: false
}, {
name: 'oneTrueWithUndefined',
values: [undefined, undefined, undefined, undefined, true],
any: true,
all: false,
not: false,
xor: true
}, {
name: 'multipleTrueWithUndefined',
values: [true, undefined, true, undefined, true],
any: true,
all: false,
not: false,
xor: false
}, {
name: 'allUndefined',
values: [undefined, undefined, undefined, undefined, undefined],
any: false,
all: false,
not: true,
xor: false
}, {
name: 'singleTrue',
values: [true],
any: true,
all: true,
not: false,
xor: true
}, {
name: 'singleFalse',
values: [false],
any: false,
all: false,
not: true,
xor: false
}, {
name: 'singleUndefined',
values: [undefined],
any: false,
all: false,
not: true,
xor: false
}
// , {
// name: 'empty',
// values: [],
// any: false,
// all: false,
// not: true,
// xor: false
// }
];
describe(`based on trigger ${TRIGGER.ANY}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.ANY);
expect(result).toEqual(test[TRIGGER.ANY])
});
});
});
describe(`based on trigger ${TRIGGER.ALL}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.ALL);
expect(result).toEqual(test[TRIGGER.ALL])
});
});
});
describe(`based on trigger ${TRIGGER.NOT}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.NOT);
expect(result).toEqual(test[TRIGGER.NOT])
});
});
});
describe(`based on trigger ${TRIGGER.XOR}`, () => {
it('should evaluate to expected result', () => {
tests.forEach(test => {
const result = evaluateResults(test.values, TRIGGER.XOR);
expect(result).toEqual(test[TRIGGER.XOR])
});
});
});
// it('should evaluate to true if trigger is NOT', () => {
// const results = {
// result: false,
// result1: false,
// result2: false
// };
// const result = computeConditionByLimit(results, 0);
// expect(result).toBeTrue();
// });
// it('should evaluate to false if trigger is NOT', () => {
// const results = {
// result: true,
// result1: false,
// result2: false
// };
// const result = computeConditionByLimit(results, 0);
// expect(result).toBeFalse();
// });
// it('should evaluate to true if trigger is XOR', () => {
// const results = {
// result: false,
// result1: true,
// result2: false
// };
// const result = computeConditionByLimit(results, 1);
// expect(result).toBeTrue();
// });
// it('should evaluate to false if trigger is XOR', () => {
// const results = {
// result: false,
// result1: true,
// result2: true
// };
// const result = computeConditionByLimit(results, 1);
// expect(result).toBeFalse();
// });
});

View File

@ -1,98 +1,144 @@
/*****************************************************************************
* 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 _ from 'lodash';
const convertToNumbers = (input) => {
let numberInputs = [];
input.forEach(inputValue => numberInputs.push(Number(inputValue)));
return numberInputs;
};
const convertToStrings = (input) => {
let stringInputs = [];
input.forEach(inputValue => stringInputs.push(inputValue !== undefined ? inputValue.toString() : ''));
return stringInputs;
};
const joinValues = (values, length) => {
return values.slice(0, length).join(', ');
};
export const OPERATIONS = [
{
name: 'equalTo',
operation: function (input) {
return input[0] === input[1];
return Number(input[0]) === Number(input[1]);
},
text: 'is equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
return ' is ' + joinValues(values, 1);
}
},
{
name: 'notEqualTo',
operation: function (input) {
return input[0] !== input[1];
return Number(input[0]) !== Number(input[1]);
},
text: 'is not equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
return ' is not ' + joinValues(values, 1);
}
},
{
name: 'greaterThan',
operation: function (input) {
return input[0] > input[1];
return Number(input[0]) > Number(input[1]);
},
text: 'is greater than',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' > ' + values[0];
return ' > ' + joinValues(values, 1);
}
},
{
name: 'lessThan',
operation: function (input) {
return input[0] < input[1];
return Number(input[0]) < Number(input[1]);
},
text: 'is less than',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' < ' + values[0];
return ' < ' + joinValues(values, 1);
}
},
{
name: 'greaterThanOrEq',
operation: function (input) {
return input[0] >= input[1];
return Number(input[0]) >= Number(input[1]);
},
text: 'is greater than or equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' >= ' + values[0];
return ' >= ' + joinValues(values, 1);
}
},
{
name: 'lessThanOrEq',
operation: function (input) {
return input[0] <= input[1];
return Number(input[0]) <= Number(input[1]);
},
text: 'is less than or equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' <= ' + values[0];
return ' <= ' + joinValues(values, 1);
}
},
{
name: 'between',
operation: function (input) {
return input[0] > input[1] && input[0] < input[2];
let numberInputs = convertToNumbers(input);
let larger = Math.max(...numberInputs.slice(1,3));
let smaller = Math.min(...numberInputs.slice(1,3));
return (numberInputs[0] > smaller) && (numberInputs[0] < larger);
},
text: 'is between',
appliesTo: ['number'],
inputCount: 2,
getDescription: function (values) {
return ' between ' + values[0] + ' and ' + values[1];
return ' is between ' + values[0] + ' and ' + values[1];
}
},
{
name: 'notBetween',
operation: function (input) {
return input[0] < input[1] || input[0] > input[2];
let numberInputs = convertToNumbers(input);
let larger = Math.max(...numberInputs.slice(1,3));
let smaller = Math.min(...numberInputs.slice(1,3));
return (numberInputs[0] < smaller) || (numberInputs[0] > larger);
},
text: 'is not between',
appliesTo: ['number'],
inputCount: 2,
getDescription: function (values) {
return ' not between ' + values[0] + ' and ' + values[1];
return ' is not between ' + values[0] + ' and ' + values[1];
}
},
{
@ -104,7 +150,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' contains ' + values[0];
return ' contains ' + joinValues(values, 1);
}
},
{
@ -116,7 +162,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' does not contain ' + values[0];
return ' does not contain ' + joinValues(values, 1);
}
},
{
@ -128,7 +174,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' starts with ' + values[0];
return ' starts with ' + joinValues(values, 1);
}
},
{
@ -140,7 +186,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' ends with ' + values[0];
return ' ends with ' + joinValues(values, 1);
}
},
{
@ -152,7 +198,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' is exactly ' + values[0];
return ' is exactly ' + joinValues(values, 1);
}
},
{
@ -182,25 +228,67 @@ export const OPERATIONS = [
{
name: 'enumValueIs',
operation: function (input) {
return input[0] === input[1];
let stringInputs = convertToStrings(input);
return stringInputs[0] === stringInputs[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
return ' is ' + joinValues(values, 1);
}
},
{
name: 'enumValueIsNot',
operation: function (input) {
return input[0] !== input[1];
let stringInputs = convertToStrings(input);
return stringInputs[0] !== stringInputs[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
return ' is not ' + joinValues(values, 1);
}
},
{
name: 'valueIs',
operation: function (input) {
const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) {
const values = input[1].split(',');
return values.find((value) => lhsValue === _.trim(value.toString()));
}
return false;
},
text: 'is one of',
appliesTo: ["string", "number"],
inputCount: 1,
getDescription: function (values) {
return ' is one of ' + values[0];
}
},
{
name: 'valueIsNot',
operation: function (input) {
const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) {
const values = input[1].split(',');
const found = values.find((value) => lhsValue === _.trim(value.toString()));
return !found;
}
return false;
},
text: 'is not one of',
appliesTo: ["string", "number"],
inputCount: 1,
getDescription: function (values) {
return ' is not one of ' + values[0];
}
}
];
export const INPUT_TYPES = {
'string': 'text',
'number': 'number'
};

View File

@ -0,0 +1,142 @@
/*****************************************************************************
* 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 { OPERATIONS } from "./operations";
let isOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIs');
let isNotOneOfOperation = OPERATIONS.find((operation) => operation.name === 'valueIsNot');
let isBetween = OPERATIONS.find((operation) => operation.name === 'between');
let isNotBetween = OPERATIONS.find((operation) => operation.name === 'notBetween');
let enumIsOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIs');
let enumIsNotOperation = OPERATIONS.find((operation) => operation.name === 'enumValueIsNot');
describe('operations', function () {
it('should evaluate isOneOf to true for number inputs', () => {
const inputs = [45, "5,6,45,8"];
expect(!!isOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isOneOf to true for string inputs', () => {
const inputs = ["45", " 45, 645, 4,8 "];
expect(!!isOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isNotOneOf to true for number inputs', () => {
const inputs = [45, "5,6,4,8"];
expect(!!isNotOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isNotOneOf to true for string inputs', () => {
const inputs = ["45", " 5,645, 4,8 "];
expect(!!isNotOneOfOperation.operation(inputs)).toBeTrue();
});
it('should evaluate isOneOf to false for number inputs', () => {
const inputs = [4, "5, 6, 7, 8"];
expect(!!isOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isOneOf to false for string inputs', () => {
const inputs = ["4", "5,645 ,7,8"];
expect(!!isOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isNotOneOf to false for number inputs', () => {
const inputs = [4, "5,4, 7,8"];
expect(!!isNotOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isNotOneOf to false for string inputs', () => {
const inputs = ["4", "5,46,4,8"];
expect(!!isNotOneOfOperation.operation(inputs)).toBeFalse();
});
it('should evaluate isBetween to true', () => {
const inputs = ["4", "3", "89"];
expect(!!isBetween.operation(inputs)).toBeTrue();
});
it('should evaluate isNotBetween to true', () => {
const inputs = ["45", "100", "89"];
expect(!!isNotBetween.operation(inputs)).toBeTrue();
});
it('should evaluate isBetween to false', () => {
const inputs = ["4", "100", "89"];
expect(!!isBetween.operation(inputs)).toBeFalse();
});
it('should evaluate isNotBetween to false', () => {
const inputs = ["45", "30", "50"];
expect(!!isNotBetween.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIs to true for number inputs', () => {
const inputs = [1, "1"];
expect(!!enumIsOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIs to true for string inputs', () => {
const inputs = ["45", "45"];
expect(!!enumIsOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIsNot to true for number inputs', () => {
const inputs = [45, "46"];
expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIsNot to true for string inputs', () => {
const inputs = ["45", "46"];
expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
});
it('should evaluate enumValueIs to false for number inputs', () => {
const inputs = [1, "2"];
expect(!!enumIsOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIs to false for string inputs', () => {
const inputs = ["45", "46"];
expect(!!enumIsOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIsNot to false for number inputs', () => {
const inputs = [45, "45"];
expect(!!enumIsNotOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIsNot to false for string inputs', () => {
const inputs = ["45", "45"];
expect(!!enumIsNotOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIs to false for undefined input', () => {
const inputs = [undefined, "45"];
expect(!!enumIsOperation.operation(inputs)).toBeFalse();
});
it('should evaluate enumValueIsNot to true for undefined input', () => {
const inputs = [undefined, "45"];
expect(!!enumIsNotOperation.operation(inputs)).toBeTrue();
});
});

View File

@ -0,0 +1,172 @@
/*****************************************************************************
* 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.
*****************************************************************************/
const NONE_VALUE = '__no_value';
const styleProps = {
backgroundColor: {
svgProperty: 'fill',
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'subobject-view');
}
},
border: {
svgProperty: 'stroke',
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view'||
type === 'subobject-view');
}
},
color: {
svgProperty: 'color',
noneValue: NONE_VALUE,
applicableForType: type => {
return !type ? true : (type === 'text-view' ||
type === 'telemetry-view'||
type === 'subobject-view');
}
},
imageUrl: {
svgProperty: 'url',
noneValue: '',
applicableForType: type => {
return !type ? false : type === 'image-view';
}
}
};
const aggregateStyleValues = (accumulator, currentStyle) => {
const styleKeys = Object.keys(currentStyle);
const properties = Object.keys(styleProps);
properties.forEach((property) => {
if (!accumulator[property]) {
accumulator[property] = [];
}
const found = styleKeys.find(key => key === property);
if (found) {
accumulator[property].push(currentStyle[found]);
}
});
return accumulator;
};
// Returns a union of styles used by multiple items.
// Styles that are common to all items but don't have the same value are added to the mixedStyles list
export const getConsolidatedStyleValues = (multipleItemStyles) => {
let aggregatedStyleValues = multipleItemStyles.reduce(aggregateStyleValues, {});
let styleValues = {};
let mixedStyles = [];
const properties = Object.keys(styleProps);
properties.forEach((property) => {
const values = aggregatedStyleValues[property];
if (values.length) {
if (values.every(value => value === values[0])) {
styleValues[property] = values[0];
} else {
styleValues[property] = '';
mixedStyles.push(property);
}
}
});
return {
styles: styleValues,
mixedStyles
};
};
const getStaticStyleForItem = (domainObject, id) => {
let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
if (domainObjectStyles) {
if (id) {
if(domainObjectStyles[id] && domainObjectStyles[id].staticStyle) {
return domainObjectStyles[id].staticStyle.style;
}
} else if (domainObjectStyles.staticStyle) {
return domainObjectStyles.staticStyle.style;
}
}
};
export const getConditionalStyleForItem = (domainObject, id) => {
let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
if (domainObjectStyles) {
if (id) {
if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
return domainObjectStyles[id].styles;
}
} else if (domainObjectStyles.staticStyle) {
return domainObjectStyles.styles;
}
}
};
//Returns either existing static styles or uses SVG defaults if available
export const getApplicableStylesForItem = (domainObject, item) => {
const type = item && item.type;
const id = item && item.id;
let style = {};
let staticStyle = getStaticStyleForItem(domainObject, id);
const properties = Object.keys(styleProps);
properties.forEach(property => {
const styleProp = styleProps[property];
if (styleProp.applicableForType(type)) {
let defaultValue;
if (staticStyle) {
defaultValue = staticStyle[property];
} else if (item) {
defaultValue = item[styleProp.svgProperty];
}
style[property] = defaultValue === undefined ? styleProp.noneValue : defaultValue;
}
});
return style;
};
export const getStylesWithoutNoneValue = (style) => {
if (_.isEmpty(style) || !style) {
return;
}
let styleObj = {};
const keys = Object.keys(style);
keys.forEach(key => {
if ((typeof style[key] === 'string')) {
if (style[key].indexOf('__no_value') > -1) {
style[key] = '';
} else {
styleObj[key] = style[key];
}
}
});
return styleObj;
};

View File

@ -0,0 +1,52 @@
/*****************************************************************************
* 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 const getLatestTimestamp = (
currentTimestamp,
compareTimestamp,
timeSystems,
currentTimeSystem
) => {
let latest = { ...currentTimestamp };
const compare = { ...compareTimestamp };
const key = currentTimeSystem.key;
if (!latest || !latest[key]) {
latest = updateLatestTimeStamp(compare, timeSystems)
}
if (compare[key] > latest[key]) {
latest = updateLatestTimeStamp(compare, timeSystems)
}
return latest;
}
function updateLatestTimeStamp(timestamp, timeSystems) {
let latest = {};
timeSystems.forEach(timeSystem => {
latest[timeSystem.key] = timestamp[timeSystem.key];
});
return latest;
}

View File

@ -0,0 +1,64 @@
/*****************************************************************************
* 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 ConditionWidgetComponent from './components/ConditionWidget.vue';
import Vue from 'vue';
export default function ConditionWidget(openmct) {
return {
key: 'conditionWidget',
name: 'Condition Widget',
cssClass: 'icon-condition-widget',
canView: function (domainObject) {
return domainObject.type === 'conditionWidget';
},
canEdit: function (domainObject) {
return domainObject.type === 'conditionWidget';
},
view: function (domainObject) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
ConditionWidgetComponent: ConditionWidgetComponent
},
provide: {
openmct,
domainObject
},
template: '<condition-widget-component></condition-widget-component>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}

View File

@ -0,0 +1,61 @@
/*****************************************************************************
* 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>
<component :is="urlDefined ? 'a' : 'span'"
class="c-condition-widget"
:href="urlDefined ? internalDomainObject.url : null"
>
<div class="c-condition-widget__label">
{{ internalDomainObject.label }}
</div>
</component>
</template>
<script>
export default {
inject: ['openmct', 'domainObject'],
data: function () {
return {
internalDomainObject: this.domainObject
}
},
computed: {
urlDefined() {
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
}
},
mounted() {
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
},
beforeDestroy() {
if (this.unlisten) {
this.unlisten();
}
},
methods: {
updateInternalDomainObject(domainObject) {
this.internalDomainObject = domainObject;
}
}
}
</script>

View File

@ -0,0 +1,60 @@
/*****************************************************************************
* 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.
*****************************************************************************/
.c-condition-widget {
$shdwSize: 3px;
@include userSelectNone();
background-color: rgba($colorBodyFg, 0.1); // Give a little presence if the user hasn't defined a fill color
border-radius: $basicCr;
border: 1px solid transparent;
display: inline-block;
padding: $interiorMarginLg $interiorMarginLg * 2;
}
a.c-condition-widget {
// Widget is conditionally made into a <a> when URL property has been defined
cursor: pointer !important;
pointer-events: inherit;
}
// Make Condition Widget expand when in a hidden frame Layout context
// For both static and Flexible Layouts
.c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
// Add some margin when a Condition Widget is in a Flexible Layout
.c-fl .c-so-view--no-frame .c-condition-widget {
@include abs(1px);
}
// When the widget is in the main view, center it in the space
.l-shell__main-container > .c-condition-widget {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

View File

@ -0,0 +1,58 @@
/*****************************************************************************
* 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 ConditionWidgetViewProvider from './ConditionWidgetViewProvider.js';
export default function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new ConditionWidgetViewProvider(openmct));
openmct.types.addType('conditionWidget', {
name: "Condition Widget",
description: "A button that can be used on its own, or dynamically styled with a Condition Set.",
creatable: true,
cssClass: 'icon-condition-widget',
initialize(domainObject) {
domainObject.label = 'Condition Widget';
},
form: [
{
"key": "label",
"name": "Label",
"control": "textfield",
property: [
"label"
],
"required": true,
"cssClass": "l-input"
},
{
"key": "url",
"name": "URL",
"control": "textfield",
"required": false,
"cssClass": "l-input-lg"
}
]
});
};
}

View File

@ -347,78 +347,6 @@ define(['lodash'], function (_) {
};
}
function getFillMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".fill";
},
icon: "icon-paint-bucket",
title: "Set fill color"
};
}
function getStrokeMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' ||
type === 'telemetry-view' ||
type === 'box-view' ||
type === 'image-view' ||
type === 'line-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".stroke";
},
icon: "icon-line-horz",
title: "Set border color"
};
}
function getTextColorMenu(selectedParent, selection) {
return {
control: "color-picker",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === 'text-view' || type === 'telemetry-view';
}),
property: function (selectionPath) {
return getPath(selectionPath) + ".color";
},
icon: "icon-font",
mandatory: true,
title: "Set text color",
preventNone: true
};
}
function getURLButton(selectedParent, selection) {
return {
control: "button",
domainObject: selectedParent,
applicableSelectedItems: selection.filter(selectionPath => {
return selectionPath[0].context.layoutItem.type === 'image-view';
}),
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-image",
title: "Edit image properties",
dialog: DIALOG_FORM.image
};
}
function getTextButton(selectedParent, selection) {
return {
control: "button",
@ -429,7 +357,7 @@ define(['lodash'], function (_) {
property: function (selectionPath) {
return getPath(selectionPath);
},
icon: "icon-gear",
icon: "icon-font",
title: "Edit text properties",
dialog: DIALOG_FORM.text
};
@ -505,14 +433,14 @@ define(['lodash'], function (_) {
let toolbar = {
'add-menu': [],
'text': [],
'url': [],
'toggle-frame': [],
'display-mode': [],
'telemetry-value': [],
'style': [],
'text-style': [],
'position': [],
'text': [],
'url': [],
'remove': []
};
@ -520,6 +448,10 @@ define(['lodash'], function (_) {
let selectedParent = selectionPath[1].context.item;
let layoutItem = selectionPath[0].context.layoutItem;
if (!layoutItem) {
return;
}
if (layoutItem.type === 'subobject-view') {
if (toolbar['add-menu'].length === 0 && selectionPath[0].context.item.type === 'layout') {
toolbar['add-menu'] = [getAddButton(selectedObjects, selectionPath)];
@ -546,15 +478,8 @@ define(['lodash'], function (_) {
if (toolbar['telemetry-value'].length === 0) {
toolbar['telemetry-value'] = [getTelemetryValueMenu(selectionPath, selectedObjects)];
}
if (toolbar.style.length < 2) {
toolbar.style = [
getFillMenu(selectedParent, selectedObjects),
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selectedObjects),
getTextSizeMenu(selectedParent, selectedObjects)
];
}
@ -571,15 +496,8 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'text-view') {
if (toolbar.style.length < 2) {
toolbar.style = [
getFillMenu(selectedParent, selectedObjects),
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
getTextColorMenu(selectedParent, selectedObjects),
getTextSizeMenu(selectedParent, selectedObjects)
];
}
@ -599,12 +517,6 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'box-view') {
if (toolbar.style.length < 2) {
toolbar.style = [
getFillMenu(selectedParent, selectedObjects),
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),
@ -618,11 +530,6 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'image-view') {
if (toolbar.style.length === 0) {
toolbar.style = [
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),
@ -632,18 +539,10 @@ define(['lodash'], function (_) {
getWidthInput(selectedParent, selectedObjects)
];
}
if (toolbar.url.length === 0) {
toolbar.url = [getURLButton(selectedParent, selectedObjects)];
}
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'line-view') {
if (toolbar.style.length === 0) {
toolbar.style = [
getStrokeMenu(selectedParent, selectedObjects)
];
}
if (toolbar.position.length === 0) {
toolbar.position = [
getStackOrder(selectedParent, selectionPath),

View File

@ -23,20 +23,20 @@
<template>
<div
v-if="isEditing"
class="c-properties"
class="c-inspect-properties"
>
<div class="c-properties__header">
<div class="c-inspect-properties__header">
Alphanumeric Format
</div>
<ul class="c-properties__section">
<li class="c-properties__row">
<ul class="c-inspect-properties__section">
<li class="c-inspect-properties__row">
<div
class="c-properties__label"
class="c-inspect-properties__label"
title="Printf formatting for the selected telemetry"
>
<label for="telemetryPrintfFormat">Format</label>
</div>
<div class="c-properties__value">
<div class="c-inspect-properties__value">
<input
id="telemetryPrintfFormat"
type="text"
@ -85,7 +85,13 @@ export default {
return;
}
let format = selection[0][0].context.layoutItem.format;
let layoutItem = selection[0][0].context.layoutItem;
if (!layoutItem) {
return;
}
let format = layoutItem.format;
this.nonMixedFormat = selection.every(selectionPath => {
return selectionPath[0].context.layoutItem.format === format;
});

View File

@ -29,6 +29,7 @@
>
<div
class="c-box-view"
:class="[styleClass]"
:style="style"
></div>
</layout-frame>
@ -36,12 +37,13 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import conditionalStylesMixin from '../mixins/objectStyles-mixin';
export default {
makeDefinition() {
return {
fill: '#717171',
stroke: 'transparent',
stroke: '',
x: 1,
y: 1,
width: 10,
@ -52,6 +54,7 @@ export default {
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin],
props: {
item: {
type: Object,
@ -71,10 +74,14 @@ export default {
},
computed: {
style() {
return {
backgroundColor: this.item.fill,
border: '1px solid ' + this.item.stroke
};
if (this.itemStyle) {
return this.itemStyle;
} else {
return {
backgroundColor: this.item.fill,
border: this.item.stroke ? '1px solid ' + this.item.stroke : ''
};
}
}
},
watch: {

View File

@ -29,6 +29,7 @@
>
<div
class="c-image-view"
:class="[styleClass]"
:style="style"
></div>
</layout-frame>
@ -36,6 +37,7 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
export default {
makeDefinition(openmct, gridSize, element) {
@ -52,6 +54,7 @@ export default {
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin],
props: {
item: {
type: Object,
@ -71,10 +74,18 @@ export default {
},
computed: {
style() {
return {
backgroundImage: 'url(' + this.item.url + ')',
border: '1px solid ' + this.item.stroke
let backgroundImage = 'url(' + this.item.url + ')';
let border = '1px solid ' + this.item.stroke;
if (this.itemStyle) {
if (this.itemStyle.imageUrl !== undefined) {
backgroundImage = 'url(' + this.itemStyle.imageUrl + ')';
}
border = this.itemStyle.border;
}
return {
backgroundImage,
border
};
}
},
watch: {

View File

@ -23,6 +23,7 @@
<template>
<div
class="l-layout__frame c-frame no-frame"
:class="[styleClass]"
:style="style"
>
<svg
@ -31,7 +32,7 @@
>
<line
v-bind="linePosition"
:stroke="item.stroke"
:stroke="stroke"
stroke-width="2"
/>
</svg>
@ -60,6 +61,8 @@
<script>
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw',
2: 'c-frame-edit__handle--se',
@ -85,6 +88,7 @@ export default {
};
},
inject: ['openmct'],
mixins: [conditionalStylesMixin],
props: {
item: {
type: Object,
@ -122,6 +126,16 @@ export default {
}
return {x, y, x2, y2};
},
stroke() {
if (this.itemStyle) {
if (this.itemStyle.border) {
return this.itemStyle.border.replace('1px solid ', '');
}
return '';
} else {
return this.item.stroke;
}
},
style() {
let {x, y, x2, y2} = this.position;
let width = Math.max(this.gridSize[0] * Math.abs(x - x2), 1);

View File

@ -45,7 +45,7 @@ import LayoutFrame from './LayoutFrame.vue'
const MINIMUM_FRAME_SIZE = [320, 180],
DEFAULT_DIMENSIONS = [10, 10],
DEFAULT_POSITION = [1, 1],
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget'];
DEFAULT_HIDDEN_FRAME_TYPES = ['hyperlink', 'summary-widget', 'conditionWidget'];
function getDefaultDimensions(gridSize) {
return MINIMUM_FRAME_SIZE.map((min, index) => {

View File

@ -30,6 +30,7 @@
<div
v-if="domainObject"
class="c-telemetry-view"
:class="styleClass"
:style="styleObject"
@contextmenu.prevent="showContextMenu"
>
@ -59,6 +60,7 @@
<script>
import LayoutFrame from './LayoutFrame.vue'
import printj from 'printj'
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
const DEFAULT_TELEMETRY_DIMENSIONS = [10, 5],
DEFAULT_POSITION = [1, 1],
@ -77,8 +79,8 @@ export default {
height: DEFAULT_TELEMETRY_DIMENSIONS[1],
displayMode: 'all',
value: metadata.getDefaultDisplayValue(),
stroke: "transparent",
fill: "transparent",
stroke: "",
fill: "",
color: "",
size: "13px"
};
@ -87,6 +89,7 @@ export default {
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin],
props: {
item: {
type: Object,
@ -122,12 +125,10 @@ export default {
return displayMode === 'all' || displayMode === 'value';
},
styleObject() {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
return Object.assign({}, {
fontSize: this.item.size
}
}, this.itemStyle);
},
fieldName() {
return this.valueMetadata && this.valueMetadata.name;

View File

@ -29,21 +29,23 @@
>
<div
class="c-text-view"
:class="[styleClass]"
:style="style"
>
{{ item.text }}
<div class="c-text-view__text">{{ item.text }}</div>
</div>
</layout-frame>
</template>
<script>
import LayoutFrame from './LayoutFrame.vue'
import conditionalStylesMixin from "../mixins/objectStyles-mixin";
export default {
makeDefinition(openmct, gridSize, element) {
return {
fill: 'transparent',
stroke: 'transparent',
fill: '',
stroke: '',
size: '13px',
color: '',
x: 1,
@ -57,6 +59,7 @@ export default {
components: {
LayoutFrame
},
mixins: [conditionalStylesMixin],
props: {
item: {
type: Object,
@ -76,12 +79,9 @@ export default {
},
computed: {
style() {
return {
backgroundColor: this.item.fill,
borderColor: this.item.stroke,
color: this.item.color,
return Object.assign({
fontSize: this.item.size
};
}, this.itemStyle);
}
},
watch: {

View File

@ -16,6 +16,8 @@
.is-editing {
/******************* STYLES FOR C-FRAME WHILE EDITING */
.c-frame {
border: 1px solid rgba($editFrameColorHov, 0.3);
&:not([s-selected]) {
&:hover {
border: $editFrameBorderHov;

View File

@ -1,6 +1,8 @@
.c-text-view {
display: flex;
align-items: stretch;
align-items: center; // Vertically center text
overflow: hidden;
padding: $interiorMargin;
.c-frame & {
@include abs();

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* 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 StyleRuleManager from "@/plugins/condition/StyleRuleManager";
import {getStylesWithoutNoneValue} from "@/plugins/condition/utils/styleUtils";
export default {
inject: ['openmct'],
data() {
return {
itemStyle: undefined,
styleClass: ''
}
},
mounted() {
this.parentDomainObject = this.$parent.domainObject;
this.itemId = this.item.id;
this.objectStyle = this.getObjectStyleForItem(this.parentDomainObject.configuration.objectStyles);
this.initObjectStyles();
},
destroyed() {
if (this.stopListeningObjectStyles) {
this.stopListeningObjectStyles();
}
},
methods: {
getObjectStyleForItem(objectStyle) {
if (objectStyle) {
return objectStyle[this.itemId] ? Object.assign({}, objectStyle[this.itemId]) : undefined;
} else {
return undefined;
}
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager(this.objectStyle, this.openmct, this.updateStyle.bind(this), true);
} else {
this.styleRuleManager.updateObjectStyleConfig(this.objectStyle);
}
if (this.stopListeningObjectStyles) {
this.stopListeningObjectStyles();
}
this.stopListeningObjectStyles = this.openmct.objects.observe(this.parentDomainObject, 'configuration.objectStyles', (newObjectStyle) => {
//Updating object styles in the inspector view will trigger this so that the changes are reflected immediately
let newItemObjectStyle = this.getObjectStyleForItem(newObjectStyle);
if (this.objectStyle !== newItemObjectStyle) {
this.objectStyle = newItemObjectStyle;
this.styleRuleManager.updateObjectStyleConfig(this.objectStyle);
}
});
},
updateStyle(style) {
this.itemStyle = getStylesWithoutNoneValue(style);
this.styleClass = this.itemStyle && this.itemStyle.isStyleInvisible;
}
}
};

View File

@ -1,17 +1,17 @@
<template>
<div class="c-properties__section c-filter-settings">
<div class="c-inspect-properties__section c-filter-settings">
<li
v-for="(filter, index) in filterField.filters"
:key="index"
class="c-properties__row c-filter-settings__setting"
class="c-inspect-properties__row c-filter-settings__setting"
>
<div
class="c-properties__label label"
class="c-inspect-properties__label label"
:disabled="useGlobal"
>
{{ filterField.name }} =
</div>
<div class="c-properties__value value">
<div class="c-inspect-properties__value value">
<!-- EDITING -->
<!-- String input, editing -->
<template v-if="!filter.possibleValues && isEditing">

View File

@ -26,17 +26,17 @@
</div>
<div v-if="expanded">
<ul class="c-properties">
<ul class="c-inspect-properties">
<div
v-if="!isEditing && persistedFilters.useGlobal"
class="c-properties__label span-all"
class="c-inspect-properties__label span-all"
>
Uses global filter
</div>
<div
v-if="isEditing"
class="c-properties__label span-all"
class="c-inspect-properties__label span-all"
>
<toggle-switch
:id="keyString"

View File

@ -23,7 +23,7 @@
</div>
<ul
v-if="expanded"
class="c-properties"
class="c-inspect-properties"
>
<filter-field
v-for="metadatum in globalMetadata"

View File

@ -7,7 +7,7 @@
}
.c-filter-tree {
// Filters UI uses a tree-based structure
.c-properties {
.c-inspect-properties {
// Add extra margin to account for filter-indicator
margin-left: 38px;
}

View File

@ -313,8 +313,4 @@
margin: 0;
}
}
.c-object-view {
display: contents;
}
}

View File

@ -232,18 +232,16 @@ export default {
this.newFrameLocation = [containerIndex, insertFrameIndex];
},
addFrame(domainObject) {
if (this.newFrameLocation.length) {
let containerIndex = this.newFrameLocation[0],
frameIndex = this.newFrameLocation[1],
frame = new Frame(domainObject.identifier),
container = this.containers[containerIndex];
let containerIndex = this.newFrameLocation.length ? this.newFrameLocation[0] : 0;
let container = this.containers[containerIndex];
let frameIndex = this.newFrameLocation.length ? this.newFrameLocation[1] : container.frames.length;
let frame = new Frame(domainObject.identifier);
container.frames.splice(frameIndex + 1, 0, frame);
sizeItems(container.frames, frame);
container.frames.splice(frameIndex + 1, 0, frame);
sizeItems(container.frames, frame);
this.newFrameLocation = [];
this.persist(containerIndex);
}
this.newFrameLocation = [];
this.persist(containerIndex);
},
deleteFrame(frameId) {
let container = this.containers

View File

@ -26,6 +26,7 @@ export default function ImageryViewProvider(openmct) {
return {
show: function (element) {
component = new Vue({
el: element,
components: {
ImageryViewLayout
},
@ -33,7 +34,6 @@ export default function ImageryViewProvider(openmct) {
openmct,
domainObject
},
el: element,
template: '<imagery-view-layout ref="ImageryLayout"></imagery-view-layout>'
});
},

View File

@ -1,90 +1,68 @@
<template>
<multipane class="c-imagery-layout"
type="vertical"
>
<pane :style="{'min-height': `300px`}">
<div class="main-image-wrapper c-imagery has-local-controls">
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
<span class="holder flex-elem grows c-imagery__lc__sliders">
<input v-model="filters.brightness"
class="icon-brightness"
type="range"
min="0"
max="500"
>
<input v-model="filters.contrast"
class="icon-contrast"
type="range"
min="0"
max="500"
>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
</div>
<div class="main-image s-image-main"
:class="{'paused unnsynced': paused(),'stale':false }"
:style="{'background-image': `url(${getImageUrl()})`,
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
>
</div>
<div class="l-image-controller flex-elem l-flex-row">
<div class="l-datetime-w flex-elem grows">
<a class="c-button show-thumbs sm hidden icon-thumbs-strip"></a>
<span class="l-time">{{ getTime() }}</span>
</div>
<div class="h-local-controls flex-elem">
<a class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></a>
</div>
</div>
</div>
</pane>
<pane class="c-inspector__elements"
handle="before"
:style="{'min-height': `100px`}"
>
<div class="c-elements-pool">
<div ref="thumbsWrapper"
class="thumbs-layout"
@scroll="handleScroll"
>
<div v-for="(imageData, index) in imageHistory"
:key="index"
class="l-image-thumb-item"
:class="{selected: imageData.selected}"
@click="setSelectedImage(imageData)"
<div class="c-imagery">
<div class="c-imagery__main-image-wrapper has-local-controls">
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover l-flex-row c-imagery__lc">
<span class="holder flex-elem grows c-imagery__lc__sliders">
<input v-model="filters.brightness"
class="icon-brightness"
type="range"
min="0"
max="500"
>
<img class="l-thumb"
:src="getImageUrl(imageData)"
>
<div class="l-time">{{ getTime(imageData) }}</div>
</div>
<input v-model="filters.contrast"
class="icon-contrast"
type="range"
min="0"
max="500"
>
</span>
<span class="holder flex-elem t-reset-btn-holder c-imagery__lc__reset-btn">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
</div>
<div class="main-image s-image-main c-imagery__main-image"
:class="{'paused unnsynced': paused(),'stale':false }"
:style="{'background-image': `url(${getImageUrl()})`,
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`}"
>
</div>
<div class="c-imagery__control-bar">
<div class="c-imagery__timestamp">{{ getTime() }}</div>
<div class="h-local-controls flex-elem">
<a class="c-button icon-pause pause-play"
:class="{'is-paused': paused()}"
@click="paused(!paused())"
></a>
</div>
</div>
</pane>
</multipane>
</div>
<div ref="thumbsWrapper"
class="c-imagery__thumbs-wrapper"
:class="{'is-paused': paused()}"
@scroll="handleScroll"
>
<div v-for="(imageData, index) in imageHistory"
:key="index"
class="c-imagery__thumb c-thumb"
:class="{selected: imageData.selected}"
@click="setSelectedImage(imageData)"
>
<img class="c-thumb__image"
:src="getImageUrl(imageData)"
>
<div class="c-thumb__timestamp">{{ getTime(imageData) }}</div>
</div>
</div>
</div>
</template>
<script>
import multipane from '@/ui/layout/multipane.vue';
import pane from '@/ui/layout/pane.vue';
import _ from 'lodash';
export default {
inject: ['openmct', 'domainObject'],
components: {
multipane,
pane
},
data() {
return {
autoScroll: true,
@ -109,7 +87,7 @@ export default {
this.subscribe(this.domainObject);
},
updated() {
this.scrollToBottom();
this.scrollToRight();
},
beforeDestroy() {
this.stopListening();
@ -152,6 +130,10 @@ export default {
if (arguments.length > 0 && state !== this.isPaused) {
this.unselectAllImages();
this.isPaused = state;
if (state === true) {
// If we are pausing, select the latest image in imageHistory
this.setSelectedImage(this.imageHistory[this.imageHistory.length - 1]);
}
if (this.nextDatum) {
this.updateValues(this.nextDatum);
@ -180,24 +162,31 @@ export default {
this.updateValues(values[values.length - 1]);
});
},
scrollToBottom() {
scrollToRight() {
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
return;
}
const scrollHeight = this.$refs.thumbsWrapper.scrollHeight || 0;
if (!scrollHeight) {
const scrollWidth = this.$refs.thumbsWrapper.scrollWidth || 0;
if (!scrollWidth) {
return;
}
setTimeout(() => this.$refs.thumbsWrapper.scrollTop = scrollHeight, 0);
setTimeout(() => this.$refs.thumbsWrapper.scrollLeft = scrollWidth, 0);
},
setSelectedImage(image) {
this.imageUrl = this.getImageUrl(image);
this.time = this.getTime(image);
this.paused(true);
this.unselectAllImages();
image.selected = true;
// If we are paused and the current image IS selected, unpause
// Otherwise, set current image and pause
if (this.isPaused && image.selected) {
this.paused(false);
this.unselectAllImages();
} else {
this.imageUrl = this.getImageUrl(image);
this.time = this.getTime(image);
this.paused(true);
this.unselectAllImages();
image.selected = true;
}
},
stopListening() {
if (this.unsubscribe) {

View File

@ -1,16 +1,20 @@
.c-imagery-layout {
.c-imagery {
display: flex;
flex-direction: column;
overflow: auto;
overflow: hidden;
height: 100%;
.main-image-wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding-bottom: 5px;
> * + * {
margin-top: $interiorMargin;
}
.main-image {
&__main-image-wrapper {
display: flex;
flex-direction: column;
flex: 1 1 auto;
}
&__main-image {
background-position: center;
background-repeat: no-repeat;
background-size: contain;
@ -21,12 +25,137 @@
}
}
.l-image-controller {
&__control-bar {
padding: 5px 0 0 0;
display: flex;
align-items: center;
}
.thumbs-layout {
margin-top: 5px;
overflow: auto;
&__timestamp {
flex: 1 1 auto;
}
&__thumbs-wrapper {
flex: 0 0 auto;
display: flex;
flex-direction: row;
height: 135px;
overflow-x: auto;
overflow-y: hidden;
&.is-paused {
background: rgba($colorPausedBg, 0.4);
}
.c-thumb:last-child {
// Hilite the lastest thumb
background: $colorBodyFg;
color: $colorBodyBg;
}
}
}
/*************************************** THUMBS */
.c-thumb {
display: flex;
flex-direction: column;
padding: 4px;
width: $imageThumbsD;
&:hover {
background: $colorThumbHoverBg;
}
&.selected {
background: $colorPausedBg !important;
color: $colorPausedFg !important;
}
&__image {
background-color: rgba($colorBodyFg, 0.2);
width: 100%;
}
&__timestamp {
flex: 0 0 auto;
padding: 2px 3px;
}
}
.l-layout,
.c-fl {
.c-imagery__thumbs-wrapper {
// When Imagery is in a layout, hide the thumbs area
display: none;
}
}
.s-image-main {
background-color: $colorPlotBg;
border: 1px solid transparent;
}
/*************************************** IMAGERY LOCAL CONTROLS*/
.c-imagery {
.h-local-controls--overlay-content {
position: absolute;
right: $interiorMargin; top: $interiorMargin;
z-index: 2;
background: $colorLocalControlOvrBg;
border-radius: $basicCr;
max-width: 200px;
min-width: 100px;
width: 35%;
align-items: center;
padding: $interiorMargin $interiorMarginLg;
input[type="range"] {
display: block;
width: 100%;
&:not(:first-child) {
margin-top: $interiorMarginLg;
}
&:before {
margin-right: $interiorMarginSm;
}
}
}
&__lc {
&__reset-btn {
$bc: $scrollbarTrackColorBg;
&:before,
&:after {
border-right: 1px solid $bc;
content:'';
display: block;
width: 5px;
height: 4px;
}
&:before {
border-top: 1px solid $bc;
margin-bottom: 2px;
}
&:after {
border-bottom: 1px solid $bc;
margin-top: 2px;
}
}
}
}
/*************************************** BUTTONS */
.c-button.pause-play {
// Pause icon set by default in markup
&.is-paused {
background: $colorPausedBg !important;
color: $colorPausedFg;
&:before {
content: $glyph-icon-play;
}
}
}

View File

@ -0,0 +1,21 @@
<template>
<div class="c-menu">
<ul>
<li
v-for="(item, index) in popupMenuItems"
:key="index"
:class="item.cssClass"
:title="item.name"
@click="item.callback"
>
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['popupMenuItems']
}
</script>

View File

@ -0,0 +1,251 @@
<template>
<div class="c-snapshot c-ne__embed">
<div v-if="embed.snapshot"
class="c-ne__embed__snap-thumb"
@click="openSnapshot()"
>
<img :src="embed.snapshot.src">
</div>
<div class="c-ne__embed__info">
<div class="c-ne__embed__name">
<a class="c-ne__embed__link"
:class="embed.cssClass"
@click="changeLocation"
>{{ embed.name }}</a>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>
<div v-if="embed.snapshot"
class="c-ne__embed__time"
>
{{ formatTime(embed.createdOn, 'YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
</div>
</template>
<script>
import Moment from 'moment';
import PopupMenu from './popup-menu.vue';
import PreviewAction from '../../../ui/preview/PreviewAction';
import Painterro from 'painterro';
import RemoveDialog from '../utils/removeDialog';
import SnapshotTemplate from './snapshot-template.html';
import Vue from 'vue';
export default {
inject: ['openmct'],
components: {
PopupMenu
},
props: {
embed: {
type: Object,
default() {
return {};
}
},
removeActionString: {
type: String,
default() {
return 'Remove This Embed';
}
}
},
data() {
return {
popupMenuItems: []
}
},
watch: {
},
mounted() {
this.addPopupMenuItems();
},
methods: {
addPopupMenuItems() {
const removeEmbed = {
cssClass: 'icon-trash',
name: this.removeActionString,
callback: this.getRemoveDialog.bind(this)
}
const preview = {
cssClass: 'icon-eye-open',
name: 'Preview',
callback: this.previewEmbed.bind(this)
}
this.popupMenuItems = [removeEmbed, preview];
},
annotateSnapshot() {
const self = this;
let save = false;
let painterroInstance = {};
const annotateVue = new Vue({
template: '<div id="snap-annotation"></div>'
});
let annotateOverlay = self.openmct.overlays.overlay({
element: annotateVue.$mount().$el,
size: 'large',
dismissable: false,
buttons: [
{
label: 'Cancel',
callback: function () {
save = false;
painterroInstance.save();
annotateOverlay.dismiss();
}
},
{
label: 'Save',
callback: function () {
save = true;
painterroInstance.save();
annotateOverlay.dismiss();
}
}
],
onDestroy: function () {
annotateVue.$destroy(true);
}
});
painterroInstance = Painterro({
id: 'snap-annotation',
activeColor: '#ff0000',
activeColorAlpha: 1.0,
activeFillColor: '#fff',
activeFillColorAlpha: 0.0,
backgroundFillColor: '#000',
backgroundFillColorAlpha: 0.0,
defaultFontSize: 16,
defaultLineWidth: 2,
defaultTool: 'ellipse',
hiddenTools: ['save', 'open', 'close', 'eraser', 'pixelize', 'rotate', 'settings', 'resize'],
translation: {
name: 'en',
strings: {
lineColor: 'Line',
fillColor: 'Fill',
lineWidth: 'Size',
textColor: 'Color',
fontSize: 'Size',
fontStyle: 'Style'
}
},
saveHandler: function (image, done) {
if (save) {
const url = image.asBlob();
const reader = new window.FileReader();
reader.readAsDataURL(url);
reader.onloadend = function () {
const snapshot = reader.result;
const snapshotObject = {
src: snapshot,
type: url.type,
size: url.size,
modified: Date.now()
};
self.embed.snapshot = snapshotObject;
self.updateEmbed(self.embed);
};
} else {
console.log('You cancelled the annotation!!!');
}
done(true);
}
}).show(this.embed.snapshot.src);
},
changeLocation() {
const link = this.embed.historicLink;
if (!link) {
return;
}
const bounds = this.openmct.time.bounds();
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
&& this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock();
window.location.href = link;
let message = '';
if (isTimeBoundChanged) {
this.openmct.time.bounds({
start: this.embed.bounds.start,
end: this.embed.bounds.end
});
message = 'Time bound values changed';
}
if (!isFixedTimespanMode) {
message = 'Time bound values changed to fixed timespan mode';
}
this.openmct.notifications.alert(message);
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);
},
getRemoveDialog() {
const options = {
name: this.removeActionString,
callback: this.removeEmbed.bind(this)
}
const removeDialog = new RemoveDialog(this.openmct, options);
removeDialog.show();
},
openSnapshot() {
const self = this;
const snapshot = new Vue({
data: () => {
return {
embed: self.embed
};
},
methods: {
formatTime: self.formatTime,
annotateSnapshot: self.annotateSnapshot
},
template: SnapshotTemplate
});
const snapshotOverlay = this.openmct.overlays.overlay({
element: snapshot.$mount().$el,
onDestroy: () => { snapshot.$destroy(true) },
size: 'large',
dismissable: true,
buttons: [
{
label: 'Done',
emphasis: true,
callback: () => {
snapshotOverlay.dismiss();
}
}
]
});
},
previewEmbed() {
const self = this;
const previewAction = new PreviewAction(self.openmct);
previewAction.invoke(JSON.parse(self.embed.objectPath));
},
removeEmbed(success) {
if (!success) {
return;
}
this.$emit('removeEmbed', this.embed.id);
},
updateEmbed(embed) {
this.$emit('updateEmbed', embed);
}
}
}
</script>

View File

@ -0,0 +1,316 @@
<template>
<div class="c-notebook__entry c-ne has-local-controls"
@dragover="dragover"
@drop.capture="dropCapture"
@drop.prevent="dropOnEntry(entry.id, $event)"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{ formatTime(entry.createdOn, 'YYYY-MM-DD') }}</span>
<span>{{ formatTime(entry.createdOn, 'HH:mm:ss') }}</span>
</div>
<div class="c-ne__content">
<div :id="entry.id"
class="c-ne__text"
:class="{'c-input-inline' : !readOnly }"
:contenteditable="!readOnly"
:style="!entry.text.length ? defaultEntryStyle : ''"
@blur="textBlur($event, entry.id)"
@focus="textFocus($event, entry.id)"
>{{ entry.text.length ? entry.text : defaultText }}</div>
<div class="c-snapshots c-ne__embeds">
<NotebookEmbed v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
:entry="entry"
@removeEmbed="removeEmbed"
@updateEmbed="updateEmbed"
/>
</div>
</div>
</div>
<div v-if="!readOnly"
class="c-ne__local-controls--hidden"
>
<button class="c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
@click="deleteEntry"
>
</button>
</div>
<div v-if="readOnly"
class="c-ne__section-and-page"
>
<a class="c-click-link"
@click="navigateToSection()"
>
{{ result.section.name }}
</a>
<span class="icon-arrow-right"></span>
<a class="c-click-link"
@click="navigateToPage()"
>
{{ result.page.name }}
</a>
</div>
</div>
</template>
<script>
import NotebookEmbed from './notebook-embed.vue';
import { createNewEmbed, getEntryPosById, getNotebookEntries } from '../utils/notebook-entries';
import Moment from 'moment';
export default {
inject: ['openmct', 'snapshotContainer'],
components: {
NotebookEmbed
},
props: {
domainObject: {
type: Object,
default() {
return {};
}
},
entry: {
type: Object,
default() {
return {};
}
},
result: {
type: Object,
default() {
return {};
}
},
selectedPage: {
type: Object,
default() {
return {};
}
},
selectedSection: {
type: Object,
default() {
return {};
}
},
readOnly: {
type: Boolean,
default() {
return true;
}
}
},
data() {
return {
currentEntryValue: '',
defaultEntryStyle: {
fontStyle: 'italic',
color: '#6e6e6e'
},
defaultText: 'add description'
}
},
watch: {
entry() {
},
readOnly(readOnly) {
},
selectedSection(selectedSection) {
},
selectedPage(selectedSection) {
}
},
mounted() {
this.updateEntries = this.updateEntries.bind(this);
},
beforeDestory() {
},
methods: {
deleteEntry() {
const self = this;
if (!self.domainObject || !self.selectedSection || !self.selectedPage || !self.entry.id) {
return;
}
const entryPosById = this.entryPosById(this.entry.id);
if (entryPosById === -1) {
return;
}
const dialog = this.openmct.overlays.dialog({
iconClass: 'alert',
message: 'This action will permanently delete this entry. Do you wish to continue?',
buttons: [
{
label: "Ok",
emphasis: true,
callback: () => {
const entries = getNotebookEntries(self.domainObject, self.selectedSection, self.selectedPage);
entries.splice(entryPosById, 1);
this.updateEntries(entries);
dialog.dismiss();
}
},
{
label: "Cancel",
callback: () => {
dialog.dismiss();
}
}
]
});
},
dragover() {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
},
dropCapture(event) {
const isEditing = this.openmct.editor.isEditing();
if (isEditing) {
this.openmct.editor.cancel();
}
},
dropOnEntry(entryId, $event) {
event.stopImmediatePropagation();
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const snapshotId = $event.dataTransfer.getData('snapshot/id');
if (snapshotId.length) {
this.moveSnapshot(snapshotId);
return;
}
const data = $event.dataTransfer.getData('openmct/domain-object-path');
const objectPath = JSON.parse(data);
const entryPos = this.entryPosById(entryId);
const bounds = this.openmct.time.bounds();
const snapshotMeta = {
bounds,
link: null,
objectPath,
openmct: this.openmct
}
const newEmbed = createNewEmbed(snapshotMeta);
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
const currentEntryEmbeds = entries[entryPos].embeds;
currentEntryEmbeds.push(newEmbed);
this.updateEntries(entries);
},
entryPosById(entryId) {
return getEntryPosById(entryId, this.domainObject, this.selectedSection, this.selectedPage);
},
findPositionInArray(array, id) {
let position = -1;
array.some((item, index) => {
const found = item.id === id;
if (found) {
position = index;
}
return found;
});
return position;
},
formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat);
},
moveSnapshot(snapshotId) {
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
this.entry.embeds.push(snapshot);
this.updateEntry(this.entry);
this.snapshotContainer.removeSnapshot(snapshotId);
},
navigateToPage() {
this.$emit('changeSectionPage', {
sectionId: this.result.section.id,
pageId: this.result.page.id
});
},
navigateToSection() {
this.$emit('changeSectionPage', {
sectionId: this.result.section.id,
pageId: null
});
},
removeEmbed(id) {
const embedPosition = this.findPositionInArray(this.entry.embeds, id);
this.entry.embeds.splice(embedPosition, 1);
this.updateEntry(this.entry);
},
selectTextInsideElement(element) {
const range = document.createRange();
range.selectNodeContents(element);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
},
textBlur($event, entryId) {
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const target = $event.target;
if (!target) {
return;
}
const entryPos = this.entryPosById(entryId);
const value = target.textContent.trim();
if (this.currentEntryValue !== value) {
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries[entryPos].text = value;
this.updateEntries(entries);
}
},
textFocus($event) {
if (this.readOnly || !this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const target = $event.target
this.currentEntryValue = target ? target.innerText : '';
if (!this.entry.text.length) {
this.selectTextInsideElement(target);
}
},
updateEmbed(newEmbed) {
let embed = this.entry.embeds.find(e => e.id === newEmbed.id);
if (!embed) {
return;
}
embed = newEmbed;
this.updateEntry(this.entry);
},
updateEntry(newEntry) {
if (!this.domainObject || !this.selectedSection || !this.selectedPage) {
return;
}
const entries = getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage);
entries.forEach(entry => {
if (entry.id === newEntry.id) {
entry = newEntry;
}
});
this.updateEntries(entries);
},
updateEntries(entries) {
this.$emit('updateEntries', entries);
}
}
}
</script>

View File

@ -0,0 +1,136 @@
<template>
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">
<button
class="c-button--menu icon-notebook"
title="Take a Notebook Snapshot"
@click="setNotebookTypes"
@click.stop="toggleMenu"
>
<span class="c-button__label"></span>
</button>
<div
v-show="showMenu"
class="c-menu"
>
<ul>
<li
v-for="(type, index) in notebookTypes"
:key="index"
:class="type.cssClass"
:title="type.name"
@click="snapshot(type)"
>
{{ type.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import Snapshot from '../snapshot';
import { clearDefaultNotebook, getDefaultNotebook } from '../utils/notebook-storage';
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
export default {
inject: ['openmct'],
props: {
domainObject: {
type: Object,
default() {
return {};
}
},
ignoreLink: {
type: Boolean,
default() {
return false;
}
},
objectPath: {
type: Array,
default() {
return null;
}
}
},
data() {
return {
notebookSnapshot: null,
notebookTypes: [],
showMenu: false
}
},
mounted() {
this.notebookSnapshot = new Snapshot(this.openmct);
document.addEventListener('click', this.hideMenu);
},
destroyed() {
document.removeEventListener('click', this.hideMenu);
},
methods: {
async setNotebookTypes() {
const notebookTypes = [];
let defaultPath = '';
const defaultNotebook = getDefaultNotebook();
if (defaultNotebook) {
const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier)
.then(d => d);
if (!domainObject.location) {
clearDefaultNotebook();
} else {
defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
}
}
if (defaultPath.length !== 0) {
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
type: NOTEBOOK_DEFAULT
});
}
notebookTypes.push({
cssClass: 'icon-notebook',
name: 'Save to Notebook Snapshots',
type: NOTEBOOK_SNAPSHOT
});
this.notebookTypes = notebookTypes;
},
toggleMenu() {
this.showMenu = !this.showMenu;
},
hideMenu() {
this.showMenu = false;
},
snapshot(notebook) {
this.hideMenu();
this.$nextTick(() => {
const element = document.querySelector('.c-overlay__contents')
|| document.getElementsByClassName('l-shell__main-container')[0];
const bounds = this.openmct.time.bounds();
const link = !this.ignoreLink
? window.location.href
: null;
const objectPath = this.objectPath || this.openmct.router.path;
const snapshotMeta = {
bounds,
link,
objectPath,
openmct: this.openmct
};
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
});
}
}
}
</script>

View File

@ -0,0 +1,129 @@
<template>
<div class="c-snapshots-h">
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<div class="l-browse-bar__object-name--w icon-notebook">
<div class="l-browse-bar__object-name">
Notebook Snapshots
<span v-if="snapshots.length"
class="l-browse-bar__object-details"
>&nbsp;{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
</span>
</div>
<PopupMenu v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems"
/>
</div>
</div>
<div class="l-browse-bar__end">
<button class="c-click-icon c-click-icon--major icon-x"
@click="close"
></button>
</div>
</div><!-- closes l-browse-bar -->
<div class="c-snapshots">
<span v-for="snapshot in snapshots"
:key="snapshot.id"
draggable="true"
@dragstart="startEmbedDrag(snapshot, $event)"
>
<NotebookEmbed ref="notebookEmbed"
:key="snapshot.id"
:embed="snapshot"
:remove-action-string="'Delete Snapshot'"
@updateEmbed="updateSnapshot"
@removeEmbed="removeSnapshot"
/>
</span>
<div v-if="!snapshots.length > 0"
class="hint"
>
There are no Notebook Snapshots currently.
</div>
</div>
</div>
</template>
<script>
import NotebookEmbed from './notebook-embed.vue';
import PopupMenu from './popup-menu.vue';
import RemoveDialog from '../utils/removeDialog';
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
export default {
inject: ['openmct', 'snapshotContainer'],
components: {
NotebookEmbed,
PopupMenu
},
props: {
toggleSnapshot: {
type: Function,
default() {
return () => {};
}
}
},
data() {
return {
popupMenuItems: [],
removeActionString: 'Delete all snapshots',
snapshots: []
}
},
mounted() {
this.addPopupMenuItems();
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
this.snapshots = this.snapshotContainer.getSnapshots();
},
beforeDestory() {
},
methods: {
addPopupMenuItems() {
const removeSnapshot = {
cssClass: 'icon-trash',
name: this.removeActionString,
callback: this.getRemoveDialog.bind(this)
}
this.popupMenuItems = [removeSnapshot];
},
close() {
this.toggleSnapshot();
},
getNotebookSnapshotMaxCount() {
return NOTEBOOK_SNAPSHOT_MAX_COUNT;
},
getRemoveDialog() {
const options = {
name: this.removeActionString,
callback: this.removeAllSnapshots.bind(this)
}
const removeDialog = new RemoveDialog(this.openmct, options);
removeDialog.show();
},
removeAllSnapshots(success) {
if (!success) {
return;
}
this.snapshotContainer.removeAllSnapshots();
},
removeSnapshot(id) {
this.snapshotContainer.removeSnapshot(id);
},
snapshotsUpdated() {
this.snapshots = this.snapshotContainer.getSnapshots();
},
startEmbedDrag(snapshot, event) {
event.dataTransfer.setData('text/plain', snapshot.id);
event.dataTransfer.setData('snapshot/id', snapshot.id);
},
updateSnapshot(snapshot) {
this.snapshotContainer.updateSnapshot(snapshot);
}
}
}
</script>

View File

@ -0,0 +1,97 @@
<template>
<div class="c-indicator c-indicator--clickable icon-notebook"
:class="[
{ 's-status-off': snapshotCount === 0 },
{ 's-status-on': snapshotCount > 0 },
{ 's-status-caution': snapshotCount === snapshotMaxCount },
{ 'has-new-snapshot': flashIndicator }
]"
>
<span class="label c-indicator__label">
{{ indicatorTitle }}
<button @click="toggleSnapshot">
{{ expanded ? 'Hide' : 'Show' }}
</button>
</span>
<span class="c-indicator__count">{{ snapshotCount }}</span>
</div>
</template>
<script>
import SnapshotContainerComponent from './notebook-snapshot-container.vue';
import { EVENT_SNAPSHOTS_UPDATED } from '../notebook-constants';
import { NOTEBOOK_SNAPSHOT_MAX_COUNT } from '../snapshot-container';
import Vue from 'vue';
export default {
inject: ['openmct','snapshotContainer'],
data() {
return {
expanded: false,
indicatorTitle: '',
snapshotCount: 0,
snapshotMaxCount: NOTEBOOK_SNAPSHOT_MAX_COUNT,
flashIndicator: false
}
},
mounted() {
this.snapshotContainer.on(EVENT_SNAPSHOTS_UPDATED, this.snapshotsUpdated);
this.updateSnapshotIndicatorTitle();
},
methods: {
notifyNewSnapshot() {
this.flashIndicator = true;
setTimeout(this.removeNotify, 15000);
},
removeNotify() {
this.flashIndicator = false;
},
snapshotsUpdated() {
if (this.snapshotContainer.getSnapshots().length > this.snapshotCount) {
this.notifyNewSnapshot();
}
this.updateSnapshotIndicatorTitle();
},
toggleSnapshot() {
this.expanded = !this.expanded;
const drawerElement = document.querySelector('.l-shell__drawer');
drawerElement.classList.toggle('is-expanded');
this.updateSnapshotContainer();
},
updateSnapshotContainer() {
const { openmct, snapshotContainer } = this;
const toggleSnapshot = this.toggleSnapshot.bind(this);
const drawerElement = document.querySelector('.l-shell__drawer');
drawerElement.innerHTML = '<div></div>';
const divElement = document.querySelector('.l-shell__drawer div');
this.component = new Vue({
provide: {
openmct,
snapshotContainer
},
el: divElement,
components: {
SnapshotContainerComponent
},
data() {
return {
toggleSnapshot
};
},
template: '<SnapshotContainerComponent :toggleSnapshot="toggleSnapshot"></SnapshotContainerComponent>'
}).$mount();
},
updateSnapshotIndicatorTitle() {
const snapshotCount = this.snapshotContainer.getSnapshots().length;
this.snapshotCount = snapshotCount;
const snapshotTitleSuffix = snapshotCount === 1
? 'Snapshot'
: 'Snapshots';
this.indicatorTitle = `${snapshotCount} ${snapshotTitleSuffix}`;
}
}
}
</script>

Some files were not shown because too many files have changed in this diff Show More