Compare commits

...

105 Commits

Author SHA1 Message Date
dc7b9a1e61 Build in production mode 2020-06-04 09:07:45 -07:00
2df62ef925 Modified plotly import 2020-06-03 19:22:16 -07:00
bdc3fdd7bb Switched plotly dependencies 2020-06-03 19:14:38 -07:00
b040af1e28 Restored plugin, suppressed plotly import 2020-06-03 18:52:18 -07:00
aed4a5ba2a Removed plotly plugin altogether: 2020-06-03 18:08:13 -07:00
32287f9108 Disable plotly plot plugin 2020-06-03 18:01:52 -07:00
c8bbeb0cae Removed plotly css 2020-06-03 17:59:20 -07:00
6b74a133d8 Upgraded karma 2020-06-03 17:41:42 -07:00
ceaf4c2ef0 Cleared circle cache 2020-06-03 16:29:55 -07:00
982b69e7e1 Don't rebuild sass every time 2020-06-03 16:25:08 -07:00
8e5182732d Used cross-platform instead of export 2020-06-03 16:24:13 -07:00
2d0b92cc38 Removed no-sandbox flag 2020-06-03 16:23:17 -07:00
75e846ea3f Added missing new line 2020-06-03 16:22:08 -07:00
1b5dee981d Reverted resource class change 2020-06-03 16:20:49 -07:00
4fee7f73e7 Fixed circle config 2020-06-03 16:19:49 -07:00
4308a4c9cf Removed report files 2020-06-03 16:17:23 -07:00
abbffcfbf1 Merge branch 'plotly-test' of https://github.com/nasa/openmct into plotly-test 2020-06-03 16:16:00 -07:00
01710876fb Increase circle resource class 2020-06-03 16:15:52 -07:00
9e3d97c85d added no-sandbox flag 2020-06-03 13:33:59 -07:00
519075001e bumped docker config to node 13 2020-06-03 13:12:45 -07:00
96a48dd9ee moved rebuild node-sass later 2020-06-03 13:08:00 -07:00
550daae76f add rebuild node-sass to config 2020-06-03 13:03:36 -07:00
b8fcb8ff14 change circleci config to node12 2020-06-03 12:45:46 -07:00
373ddd0bf5 updated circleci to use node13 2020-06-03 11:47:00 -07:00
d62cc6b3ee bumped karma-jasmine to ^2.0.0 2020-06-03 11:11:07 -07:00
5116d38437 added test memory increase 2020-06-03 11:03:36 -07:00
29771f2722 Merge branch 'master' of https://github.com/nasa/openmct into plotly-test 2020-06-02 16:52:34 -07:00
7bfe4bb25c restored karma-jasmine to original 2020-06-02 16:31:04 -07:00
510c637081 locked karma-jasmine dependency 2020-06-02 16:01:02 -07:00
02b537580c Merge pull request #3057 from nasa/code-standards-update
Updated code standard
2020-06-02 13:31:21 -07:00
d7c65fec4c changed jasmine-core to ^2.0.0 2020-06-02 13:11:18 -07:00
7073b0717f Merge branch 'master' into code-standards-update 2020-06-02 13:05:32 -07:00
d6bb1b2a12 Added note about requiring a single return statement. 2020-06-02 13:05:15 -07:00
626e2d8e80 changed jasmine-core to 2.0.0 to test circle-ci specfilter issue 2020-06-02 12:56:51 -07:00
1fe673c1f5 Merge branch 'plotly-test' of https://github.com/nasa/openmct into plotly-test 2020-06-02 12:44:35 -07:00
d9b00574e7 Merge branch 'master' of https://github.com/nasa/openmct into plotly-test 2020-06-02 12:38:39 -07:00
7c48b3ba9a Merge branch 'master' into plotly-test 2020-06-02 12:29:46 -07:00
c256696790 Image view should react to time conductor changes - 2712 (#2934)
Image view reacts to time conductor changes
Fixes #2712
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-06-02 12:27:55 -07:00
b6e589eed4 removed telemetry from initialization config 2020-06-01 14:46:54 -07:00
d5480e7524 Multiple item conditional styles (#3017)
Conditional Styles for multiple items #3076

* Preview condition styles on selecting that condition or one of it's styles
Do not evaluate conditional styles in edit mode

* Conditions styling tweaked

- Condition match `is-current` styling for browse and edit modes;
- Disallowed pointer events on Conditions to prevent selection in
Inspector when not editing;

* Highlight current condition in conditionSet view

* Addresses review comments.

* Condition matching highlighting tweaked

- Enable match highlighting during Editing;
- Tweaks to `is-current` styling;

* Don't reset the callback on destroy

* Combine multiple and single selection styling of objects

* Fix issue with applying styles in edit mode

* Fix item styles bug

* Remove comment

* Adds back visibility toggle

* Set isEditing on initialization.

* Addresses review comments - removes use of lodash.

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-06-01 12:40:40 -07:00
ab463e93fe Refactor Notifications to use Vue, and add clear all option (#3068)
[Notifications] Need a clear all notifications option #3006

* create new notifications indicator in vue, and sunset old one

* add notifications list overlay

* working notifications

* add support for progress notifications

* update percentage on mounted

* Markup cleanups in new Vue Notifications files

- Removed unneeded markup and class wrappers;
- Removed unneeded inline styling;

* remove example notifications

* fix lint errors

* make reviewer requested changes, remove old not needed files, add test

* update testTools to testUtils

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-06-01 11:45:33 -07:00
fb1813c14b change in legend position, trace names 2020-06-01 07:40:24 -07:00
cce834f873 changed colors, added legend, fixed removeTelemetry 2020-05-29 15:14:05 -07:00
ca60d02614 added name to plugin function, changed colors 2020-05-29 13:38:52 -07:00
9520c09b49 fixed updateData so plot is extended in fixed timespan 2020-05-29 12:02:31 -07:00
0d70717b35 addressed review comments 2020-05-29 09:55:37 -07:00
f18da542d6 added updateRange function, removed length arg from updateData, fixed unsubscribe 2020-05-29 09:37:56 -07:00
e7c38d473b added unsubscribe, subscribeTo methods 2020-05-28 15:26:30 -07:00
2f8db31a33 added dynamic layout for fixed, local clock 2020-05-28 15:25:30 -07:00
1c58d8c85f implemented Plotly.relayout to set x-axis correctly for local clock 2020-05-28 15:25:05 -07:00
baf426055c added back if in updateData 2020-05-28 15:23:04 -07:00
aa3af520ea refactor of refreshData 2020-05-28 15:21:15 -07:00
4a43893ccc debugging local clock issues 2020-05-28 15:21:01 -07:00
77f50a41c9 added some comments 2020-05-28 15:20:38 -07:00
e4cd5f441f added requestHistory 2020-05-28 15:19:44 -07:00
a9cfb002fb added listeners and bounds methods 2020-05-28 15:15:39 -07:00
b603a5b722 WIP... 2020-05-28 15:13:35 -07:00
be165761c7 WIP: adding bounds functions 2020-05-28 15:13:10 -07:00
da88cf58cc add, remove multiple traces, remove hover effects 2020-05-28 15:12:48 -07:00
bc2bd53f9b WIP 2020-05-28 15:12:11 -07:00
6b7fd5f22a import RemoveAction 2020-05-28 15:09:01 -07:00
4ee214a142 add code to set Y-axis and plot multiple telemetry types 2020-05-28 15:07:35 -07:00
4af24db38a changed mode and type, dropped old data as data extends 2020-05-28 15:06:24 -07:00
8db27809ef rendering a plotly plot from SWG 2020-05-28 15:04:36 -07:00
3116b1addd wip: added telemetry provider 2020-05-28 14:58:24 -07:00
5f67c45b50 working on viewProvider 2020-05-28 14:57:52 -07:00
8158afc29a added hardcoded test plot 2020-05-28 14:57:13 -07:00
13b3acb7e0 basic plotly plugin structure 2020-05-28 14:55:06 -07:00
8363c65312 [Notebook] display bounds change notification only if bounds changed (#3062) Resolves (#3051)
* display bounds change notification only if bounds changed

* avoid empty notification messages.
2020-05-28 12:29:32 -07:00
04598b6cf1 Bug fixes for plots (#3019) (#3069)
* prevent plots from breaking when more than one NaN is received.

* fix y axis label issue

* dont emit a viewport change event when marquee doesnt happen
2020-05-28 09:58:22 -07:00
43628ad9d6 Lodash upgrade and cleanup (#2990)
* Upgrades lodash
* Replaces some usage of lodash with native functions.
* Adds linting to catch cases where native functions could be used instead of lodash functions
* Renamed testTools to testUtils

Co-authored-by: Joshi <simplyrender@gmail.com>
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-05-27 10:59:02 -07:00
67bea86bc8 Added headless mode start option (#3065)
* Added headless mode start option. Fixes #3064
2020-05-26 11:39:55 -07:00
4eb4cbfffc Merge pull request #3020 from nasa/updated-checklists
Add details about pull requests to contributing guide
2020-05-19 09:30:05 -07:00
eda01abcbc Merge branch 'master' into updated-checklists 2020-05-18 12:12:26 -07:00
2fa29124bf Merge branch 'master' into code-standards-update 2020-05-18 11:45:27 -07:00
694b8f4666 provide format for name metadata (#3054) 2020-05-18 11:40:05 -07:00
33faeafa98 New proposed ESLint rules that require no code changes 2020-05-16 16:19:36 -07:00
a40ff07353 Updated guidance on anonymous functions 2020-05-16 16:11:42 -07:00
41dc9c794d Fixed capitalization on CONST 2020-05-16 16:08:43 -07:00
f1faf3965d Changed reference to constructors to classes 2020-05-16 16:07:49 -07:00
da2ecbbcad Fixed formatting issues, removed outdated example code. 2020-05-16 16:06:59 -07:00
32c892fe98 Updated code standards 2020-05-16 15:56:30 -07:00
bbb271a678 clarify value hints (#2673)
remove confusing comments regarding domain - input and range - output
2020-05-15 14:48:25 -07:00
fec1438806 dont emit a viewport change event when marquee doesnt happen (#3036)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-05-15 14:35:13 -07:00
28f19ec310 [Notebook] Clicking on an embed in a notebook entry does not result in bounds change #3034 (#3042) 2020-05-14 14:56:41 -07:00
f934454c25 Fixes computation of result when telemetry is not used by an object (#3040)
* Resolves issue #30307
Don't compute the result of a condition if telemetry datum is not being used by that condition

* Adds tests for condition results
2020-05-13 14:30:45 -07:00
eb49ffae02 Update CONTRIBUTING.md 2020-05-13 09:21:00 -07:00
5751012872 Update CONTRIBUTING.md 2020-05-13 09:20:03 -07:00
aa041e04cf Merge remote-tracking branch 'origin/update-contributing-guidelines' into updated-checklists 2020-05-11 17:13:53 -07:00
24e7ea143a Added more details on the process around pull requests 2020-05-11 17:12:25 -07:00
79d5d9c4d0 Update CONTRIBUTING.md 2020-05-11 16:15:17 -07:00
b5bfdc4418 Update CONTRIBUTING.md 2020-05-11 15:56:49 -07:00
59730c60ec Update CONTRIBUTING.md 2020-05-11 15:54:25 -07:00
4a87a5d847 Show object styles in preview modal (#3018)
* Adds conditional styles to Preview window
2020-05-11 14:25:39 -07:00
421c09ec2c Allow users to lazy load Tabs (#2958)
* lazy load tabs

* remove listener on destroy

* fix lint error

* Store current tab position on domainObject

* remove lodash dependency and use keystring
2020-05-08 10:36:13 -07:00
0679b246b8 [Notebook]: Remove default section/page from localstorage on notebook delete (#2900)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-05-07 16:13:32 -07:00
83f9c6c528 improve plot gestures - and clean up (#3013) 2020-05-06 10:33:59 -07:00
a5f3ba6259 Use evalAsync instead of digest() (#3001)
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-05-05 12:23:41 -07:00
a70facf0c8 Merge pull request #3000 from nasa/upgrade-moment
Upgrade moment to 2.25.3
2020-05-05 11:01:39 -07:00
447fe94325 Merge branch 'upgrade-moment' of https://github.com/nasa/openmct into upgrade-moment 2020-05-05 10:36:43 -07:00
8e2b666766 Upgraded moment version to 2.25.3 2020-05-05 10:36:33 -07:00
dcbfbdbb89 Merge branch 'master' into upgrade-moment 2020-05-05 10:12:50 -07:00
4c76bf34ab Highlight currently winning Condition in Condition Set Edit and Read-only views (#2936)
* Preview condition styles on selecting that condition or one of it's styles

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-05-05 09:55:42 -07:00
81b7a9d3e0 Merge branch 'master' into upgrade-moment 2020-05-01 16:47:26 -07:00
dc573c479c Upgrade moment to 2.24.0 2020-05-01 16:25:49 -07:00
115 changed files with 2049 additions and 584 deletions

View File

@ -2,7 +2,7 @@ version: 2
jobs: jobs:
build: build:
docker: docker:
- image: circleci/node:8-browsers - image: circleci/node:13-browsers
environment: environment:
CHROME_BIN: "/usr/bin/google-chrome" CHROME_BIN: "/usr/bin/google-chrome"
steps: steps:
@ -11,7 +11,7 @@ jobs:
name: Update npm name: Update npm
command: 'sudo npm install -g npm@latest' command: 'sudo npm install -g npm@latest'
- restore_cache: - restore_cache:
key: dependency-cache-{{ checksum "package.json" }} key: dependency-cache-node-13-{{ checksum "package.json" }}
- run: - run:
name: Installing dependencies (npm install) name: Installing dependencies (npm install)
command: npm install command: npm install

View File

@ -1,3 +1,4 @@
const LEGACY_FILES = ["platform/**", "example/**"];
module.exports = { module.exports = {
"env": { "env": {
"browser": true, "browser": true,
@ -10,7 +11,8 @@ module.exports = {
}, },
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:vue/recommended" "plugin:vue/recommended",
"plugin:you-dont-need-lodash-underscore/compatible"
], ],
"parser": "vue-eslint-parser", "parser": "vue-eslint-parser",
"parserOptions": { "parserOptions": {
@ -22,6 +24,9 @@ module.exports = {
} }
}, },
"rules": { "rules": {
"you-dont-need-lodash-underscore/omit": "off",
"you-dont-need-lodash-underscore/throttle": "off",
"you-dont-need-lodash-underscore/flatten": "off",
"no-bitwise": "error", "no-bitwise": "error",
"curly": "error", "curly": "error",
"eqeqeq": "error", "eqeqeq": "error",
@ -66,6 +71,56 @@ module.exports = {
], ],
"dot-notation": "error", "dot-notation": "error",
"indent": ["error", 4], "indent": ["error", 4],
// https://eslint.org/docs/rules/no-case-declarations
"no-case-declarations": "error",
// https://eslint.org/docs/rules/max-classes-per-file
"max-classes-per-file": ["error", 1],
// https://eslint.org/docs/rules/no-eq-null
"no-eq-null": "error",
// https://eslint.org/docs/rules/no-eval
"no-eval": "error",
// https://eslint.org/docs/rules/no-floating-decimal
"no-floating-decimal": "error",
// https://eslint.org/docs/rules/no-implicit-globals
"no-implicit-globals": "error",
// https://eslint.org/docs/rules/no-implied-eval
"no-implied-eval": "error",
// https://eslint.org/docs/rules/no-lone-blocks
"no-lone-blocks": "error",
// https://eslint.org/docs/rules/no-loop-func
"no-loop-func": "error",
// https://eslint.org/docs/rules/no-new-func
"no-new-func": "error",
// https://eslint.org/docs/rules/no-new-wrappers
"no-new-wrappers": "error",
// https://eslint.org/docs/rules/no-octal-escape
"no-octal-escape": "error",
// https://eslint.org/docs/rules/no-proto
"no-proto": "error",
// https://eslint.org/docs/rules/no-return-await
"no-return-await": "error",
// https://eslint.org/docs/rules/no-script-url
"no-script-url": "error",
// https://eslint.org/docs/rules/no-self-compare
"no-self-compare": "error",
// https://eslint.org/docs/rules/no-sequences
"no-sequences": "error",
// https://eslint.org/docs/rules/no-unmodified-loop-condition
"no-unmodified-loop-condition": "error",
// https://eslint.org/docs/rules/no-useless-call
"no-useless-call": "error",
// https://eslint.org/docs/rules/wrap-iife
"wrap-iife": "error",
// https://eslint.org/docs/rules/no-nested-ternary
"no-nested-ternary": "error",
// https://eslint.org/docs/rules/switch-colon-spacing
"switch-colon-spacing": "error",
// https://eslint.org/docs/rules/no-useless-computed-key
"no-useless-computed-key": "error",
// https://eslint.org/docs/rules/rest-spread-spacing
"rest-spread-spacing": ["error"],
"vue/html-indent": [ "vue/html-indent": [
"error", "error",
4, 4,
@ -112,6 +167,13 @@ module.exports = {
} }
] ]
} }
}, {
"files": LEGACY_FILES,
"rules": {
// https://eslint.org/docs/rules/no-nested-ternary
"no-nested-ternary": "off",
"no-var": "off"
}
} }
] ]
}; };

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ protractor/logs
npm-debug.log npm-debug.log
package-lock.json package-lock.json
report.*.json

4
API.md
View File

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

View File

@ -103,7 +103,7 @@ the name chosen could not be mistaken for a topic or master branch.
### Merging ### Merging
When development is complete on an issue, the first step toward merging it When development is complete on an issue, the first step toward merging it
back into the master branch is to file a Pull Request. The contributions back into the master branch is to file a Pull Request (PR). The contributions
should meet code, test, and commit message standards as described below, should meet code, test, and commit message standards as described below,
and the pull request should include a completed author checklist, also and the pull request should include a completed author checklist, also
as described below. Pull requests may be assigned to specific team as described below. Pull requests may be assigned to specific team
@ -114,6 +114,15 @@ request. When the reviewer is satisfied, they should add a comment to
the pull request containing the reviewer checklist (from below) and complete the pull request containing the reviewer checklist (from below) and complete
the merge back to the master branch. the merge back to the master branch.
Additionally:
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull requests __author__. If no issue exists, create one.
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull requests __author__.
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
* Every PR must have two reviewers assigned, though only one approval is necessary for merge.
* Changes to API require approval by a senior developer.
* When creating a PR, it is the author's responsibility to apply any priority label from the issue to the PR as well. This helps with prioritization.
## Standards ## Standards
Contributions to Open MCT are expected to meet the following standards. Contributions to Open MCT are expected to meet the following standards.
@ -122,89 +131,96 @@ changes.
### Code Standards ### Code Standards
JavaScript sources in Open MCT must satisfy JSLint under its default JavaScript sources in Open MCT must satisfy the ESLint rules defined in
settings. This is verified by the command line build. this repository. This is verified by the command line build.
#### Code Guidelines #### Code Guidelines
JavaScript sources in Open MCT should: JavaScript sources in Open MCT should:
* Use four spaces for indentation. Tabs should not be used. 1. Write clean code. Heres a good summary - https://github.com/ryanmcdermott/clean-code-javascript.
* Include JSDoc for any exposed API (e.g. public methods, constructors). 1. Include JSDoc for any exposed API (e.g. public methods, classes).
* Include non-JSDoc comments as-needed for explaining private variables, 1. Include non-JSDoc comments as-needed for explaining private variables,
methods, or algorithms when they are non-obvious. methods, or algorithms when they are non-obvious. Otherwise code
* Define one public class per script, expressed as a constructor function should be self-documenting.
returned from an AMD-style module. 1. Classes and Vue components should use camel case, first letter capitalized
* Follow “Java-like” naming conventions. These includes: (e.g. SomeClassName).
* Classes should use camel case, first letter capitalized 1. Methods, variables, fields, events, and function names should use camelCase,
(e.g. SomeClassName). first letter lower-case (e.g. someVariableName).
* Methods, variables, fields, and function names should use camel case, 1. Source files that export functions should use camelCase, first letter lower-case (eg. testTools.js)
first letter lower-case (e.g. someVariableName). 1. Constants (variables or fields which are meant to be declared and
* Constants (variables or fields which are meant to be declared and initialized statically, and never changed) should use only capital
initialized statically, and never changed) should use only capital letters, with underscores between words (e.g. SOME_CONSTANT). They should always be declared as `const`s
letters, with underscores between words (e.g. SOME_CONSTANT). 1. File names should be the name of the exported class, plus a .js extension
* File names should be the name of the exported class, plus a .js extension (e.g. SomeClassName.js).
(e.g. SomeClassName.js). 1. Avoid anonymous functions, except when functions are short (one or two lines)
* Avoid anonymous functions, except when functions are short (a few lines) and their inclusion makes sense within the flow of the code
and/or their inclusion makes sense within the flow of the code (e.g. as arguments to a forEach call). Anonymous functions should always be arrow functions.
(e.g. as arguments to a forEach call). 1. Named functions are preferred over functions assigned to variables.
* Avoid deep nesting (especially of functions), except where necessary eg.
(e.g. due to closure scope). ```JavaScript
* End with a single new-line character. function renameObject(object, newName) {
* Expose public methods by declaring them on the class's prototype. Object.name = newName;
* Within a given function's scope, do not mix declarations and imperative }
code, and present these in the following order: ```
* First, variable declarations and initialization. is preferable to
* Second, function declarations. ```JavaScript
* Third, imperative statements. const rename = (object, newName) => {
* Finally, the returned value. Object.name = newName;
}
```
1. Avoid deep nesting (especially of functions), except where necessary
(e.g. due to closure scope).
1. End with a single new-line character.
1. Always use ES6 `Class`es and inheritence rather than the pre-ES6 prototypal
pattern.
1. Within a given function's scope, do not mix declarations and imperative
code, and present these in the following order:
* First, variable declarations and initialization.
* Secondly, imperative statements.
* Finally, the returned value. Functions should only have a single return statement.
1. Avoid the use of "magic" values.
eg.
```JavaScript
Const UNAUTHORIZED = 401
if (responseCode === UNAUTHORIZED)
```
is preferable to
```JavaScript
if (responseCode === 401)
```
1. Dont use the ternary operator. Yes it's terse, but there's probably a clearer way of writing it.
1. Test specs should reside alongside the source code they test, not in a separate directory.
1. Organize code by feature, not by type.
eg.
```
- telemetryTable
- row
TableRow.js
TableRowCollection.js
TableRow.vue
- column
TableColumn.js
TableColumn.vue
plugin.js
pluginSpec.js
```
is preferable to
```
- telemetryTable
- components
TableRow.vue
TableColumn.vue
- collections
TableRowCollection.js
TableColumn.js
TableRow.js
plugin.js
pluginSpec.js
```
Deviations from Open MCT code style guidelines require two-party agreement, Deviations from Open MCT code style guidelines require two-party agreement,
typically from the author of the change and its reviewer. typically from the author of the change and its reviewer.
#### Code Example
```js
/*global define*/
/**
* Bundles should declare themselves as namespaces in whichever source
* file is most like the "main point of entry" to the bundle.
* @namespace some/bundle
*/
define(
['./OtherClass'],
function (OtherClass) {
"use strict";
/**
* A summary of how to use this class goes here.
*
* @constructor
* @memberof some/bundle
*/
function ExampleClass() {
}
// Methods which are not intended for external use should
// not have JSDoc (or should be marked @private)
ExampleClass.prototype.privateMethod = function () {
};
/**
* A summary of this method goes here.
* @param {number} n a parameter
* @returns {number} a return value
*/
ExampleClass.prototype.publicMethod = function (n) {
return n * 2;
}
return ExampleClass;
}
);
```
### Test Standards ### Test Standards
Automated testing shall occur whenever changes are merged into the main Automated testing shall occur whenever changes are merged into the main
@ -292,6 +308,7 @@ checklist).
2. Unit tests included and/or updated with changes? 2. Unit tests included and/or updated with changes?
3. Command line build passes? 3. Command line build passes?
4. Changes have been smoke-tested? 4. Changes have been smoke-tested?
5. Testing instructions included?
### Reviewer Checklist ### Reviewer Checklist
@ -299,3 +316,4 @@ checklist).
2. Appropriate unit tests included? 2. Appropriate unit tests included?
3. Code style and in-line documentation are appropriate? 3. Code style and in-line documentation are appropriate?
4. Commit messages meet standards? 4. Commit messages meet standards?
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)

View File

@ -50,7 +50,8 @@ define([
values: [ values: [
{ {
key: "name", key: "name",
name: "Name" name: "Name",
format: "string"
}, },
{ {
key: "utc", key: "utc",
@ -99,7 +100,7 @@ define([
}; };
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) { GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return _.extend( return Object.assign(
{}, {},
domainObject.telemetry, domainObject.telemetry,
METADATA_BY_TYPE[domainObject.type] METADATA_BY_TYPE[domainObject.type]

View File

@ -52,6 +52,7 @@ define([
return { return {
name: name, name: name,
utc: Math.floor(timestamp / 5000) * 5000, utc: Math.floor(timestamp / 5000) * 5000,
local: Math.floor(timestamp / 5000) * 5000,
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length] url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
}; };
} }
@ -78,7 +79,7 @@ define([
}, },
request: function (domainObject, options) { request: function (domainObject, options) {
var start = options.start; var start = options.start;
var end = options.end; var end = Math.min(options.end, Date.now());
var data = []; var data = [];
while (start <= end && data.length < 5000) { while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, domainObject.name)); data.push(pointForTimestamp(start, domainObject.name));
@ -118,6 +119,14 @@ define([
name: 'Time', name: 'Time',
key: 'utc', key: 'utc',
format: 'utc', format: 'utc',
hints: {
domain: 2
}
},
{
name: 'Local Time',
key: 'local',
format: 'local-format',
hints: { hints: {
domain: 1 domain: 1
} }

View File

@ -2,7 +2,6 @@
"name": "openmct", "name": "openmct",
"version": "1.0.0-snapshot", "version": "1.0.0-snapshot",
"description": "The Open MCT core platform", "description": "The Open MCT core platform",
"dependencies": {},
"devDependencies": { "devDependencies": {
"angular": "1.7.9", "angular": "1.7.9",
"angular-route": "1.4.14", "angular-route": "1.4.14",
@ -24,6 +23,7 @@
"d3-time-format": "2.1.x", "d3-time-format": "2.1.x",
"eslint": "5.2.0", "eslint": "5.2.0",
"eslint-plugin-vue": "^6.0.0", "eslint-plugin-vue": "^6.0.0",
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
"eventemitter3": "^1.2.0", "eventemitter3": "^1.2.0",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"express": "^4.13.1", "express": "^4.13.1",
@ -38,27 +38,28 @@
"istanbul-instrumenter-loader": "^3.0.1", "istanbul-instrumenter-loader": "^3.0.1",
"jasmine-core": "^3.1.0", "jasmine-core": "^3.1.0",
"jsdoc": "^3.3.2", "jsdoc": "^3.3.2",
"karma": "^2.0.3", "karma": "5.0.9",
"karma-chrome-launcher": "^2.2.0", "karma-chrome-launcher": "3.1.0",
"karma-cli": "^1.0.1", "karma-cli": "^1.0.1",
"karma-coverage": "^1.1.2", "karma-coverage": "^1.1.2",
"karma-coverage-istanbul-reporter": "^2.1.1", "karma-coverage-istanbul-reporter": "^2.1.1",
"karma-html-reporter": "^0.2.7", "karma-html-reporter": "^0.2.7",
"karma-jasmine": "^1.1.2", "karma-jasmine": "^2.0.0",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^3.0.0", "karma-webpack": "^3.0.0",
"location-bar": "^3.0.1", "location-bar": "^3.0.1",
"lodash": "^3.10.1", "lodash": "^4.17.12",
"markdown-toc": "^0.11.7", "markdown-toc": "^0.11.7",
"marked": "^0.3.5", "marked": "^0.3.5",
"mini-css-extract-plugin": "^0.4.1", "mini-css-extract-plugin": "^0.4.1",
"minimist": "^1.1.1", "minimist": "^1.1.1",
"moment": "^2.11.1", "moment": "2.25.3",
"moment-duration-format": "^2.2.2", "moment-duration-format": "^2.2.2",
"moment-timezone": "^0.5.21", "moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3", "node-bourbon": "^4.2.3",
"node-sass": "^4.9.2", "node-sass": "^4.9.2",
"painterro": "^0.2.65", "painterro": "^0.2.65",
"plotly.js": "^1.54.1",
"printj": "^1.2.1", "printj": "^1.2.1",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"request": "^2.69.0", "request": "^2.69.0",
@ -83,9 +84,9 @@
"build:prod": "cross-env NODE_ENV=production webpack", "build:prod": "cross-env NODE_ENV=production webpack",
"build:dev": "webpack", "build:dev": "webpack",
"build:watch": "webpack --watch", "build:watch": "webpack --watch",
"test": "karma start --single-run", "test": "cross-env NODE_OPTIONS=\"--max-old-space-size=4096\" karma start --single-run",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run", "test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:coverage": "./scripts/test-coverage.sh", "test:coverage": "cross-env NODE_ENV=production COVERAGE=true NODE_OPTIONS=\"--max-old-space-size=4096\" karma start --single-run",
"test:watch": "karma start --no-single-run", "test:watch": "karma start --no-single-run",
"verify": "concurrently 'npm:test' 'npm:lint'", "verify": "concurrently 'npm:test' 'npm:lint'",
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api", "jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",

View File

@ -6,6 +6,12 @@
ng-show="ngModel.dialog.messages.length > 1 || ng-show="ngModel.dialog.messages.length > 1 ||
ngModel.dialog.messages.length == 0">s</span> ngModel.dialog.messages.length == 0">s</span>
</div> </div>
<button
ng-if="ngModel.dialog.topBarButton"
class="c-button c-button--major"
ng-click="ngModel.topBarButton.onClick">
{{ ngModel.topBarButton.label }}
</button>
</div> </div>
<div class="w-messages c-overlay__messages"> <div class="w-messages c-overlay__messages">
<mct-include <mct-include
@ -16,7 +22,7 @@
<button ng-repeat="dialogAction in ngModel.dialog.actions" <button ng-repeat="dialogAction in ngModel.dialog.actions"
class="c-button c-button--major" class="c-button c-button--major"
ng-click="dialogAction.action()"> ng-click="dialogAction.action()">
{{dialogAction.label}} {{ dialogAction.label }}
</button> </button>
</div> </div>
</div> </div>

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define( define(
['../../../../../src/api/objects/object-utils'], ['objectUtils'],
function (objectUtils) { function (objectUtils) {
/** /**

View File

@ -21,44 +21,15 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
"./src/NotificationIndicatorController", "./src/NotificationService"
"./src/NotificationIndicator",
"./src/NotificationService",
"./res/notification-indicator.html"
], function ( ], function (
NotificationIndicatorController, NotificationService
NotificationIndicator,
NotificationService,
notificationIndicatorTemplate
) { ) {
return { return {
name:"platform/commonUI/notification", name:"platform/commonUI/notification",
definition: { definition: {
"extensions": { "extensions": {
"templates": [
{
"key": "notificationIndicatorTemplate",
"template": notificationIndicatorTemplate
}
],
"controllers": [
{
"key": "NotificationIndicatorController",
"implementation": NotificationIndicatorController,
"depends": [
"$scope",
"openmct",
"dialogService"
]
}
],
"indicators": [
{
"implementation": NotificationIndicator,
"priority": "fallback"
}
],
"services": [ "services": [
{ {
"key": "notificationService", "key": "notificationService",

View File

@ -1,8 +0,0 @@
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
<div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell"
ng-controller="NotificationIndicatorController">
<span class="label c-indicator__label">
<button ng-click="showNotificationsList()">
{{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></button>
</span><span class="c-indicator__count">{{notifications.length}}</span>
</div>

View File

@ -1,73 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
[],
function () {
/**
* Provides an indicator that is visible when there are
* banner notifications that have been minimized. Will also indicate
* the number of notifications. Notifications can be viewed by
* clicking on the indicator to launch a dialog showing a list of
* notifications.
* @param $scope
* @param notificationService
* @param dialogService
* @constructor
*/
function NotificationIndicatorController($scope, openmct, dialogService) {
$scope.notifications = openmct.notifications.notifications;
$scope.highest = openmct.notifications.highest;
/**
* Launch a dialog showing a list of current notifications.
*/
$scope.showNotificationsList = function () {
let notificationsList = openmct.notifications.notifications.map(notification => {
if (notification.model.severity === 'alert' || notification.model.severity === 'info') {
notification.model.primaryOption = {
label: 'Dismiss',
callback: () => {
let currentIndex = notificationsList.indexOf(notification);
notification.dismiss();
notificationsList.splice(currentIndex, 1);
}
}
}
return notification;
})
dialogService.getDialogResponse('overlay-message-list', {
dialog: {
title: "Messages",
//Launch the message list dialog with the models
// from the notifications
messages: notificationsList
}
});
};
}
return NotificationIndicatorController;
}
);

View File

@ -1,60 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['../src/NotificationIndicatorController'],
function (NotificationIndicatorController) {
xdescribe("The notification indicator controller ", function () {
var mockNotificationService,
mockScope,
mockDialogService,
controller;
beforeEach(function () {
mockNotificationService = jasmine.createSpy("notificationService");
mockScope = jasmine.createSpy("$scope");
mockDialogService = jasmine.createSpyObj(
"dialogService",
["getDialogResponse","dismiss"]
);
mockNotificationService.highest = {
severity: "error"
};
controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
});
it("exposes the highest notification severity to the template", function () {
expect(mockScope.highest).toBeTruthy();
expect(mockScope.highest.severity).toBe("error");
});
it("invokes the dialog service to show list of messages", function () {
expect(mockScope.showNotificationsList).toBeDefined();
mockScope.showNotificationsList();
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
expect(mockDialogService.getDialogResponse.calls.mostRecent().args[0]).toBe('overlay-message-list');
expect(mockDialogService.getDialogResponse.calls.mostRecent().args[1].dialog).toBeDefined();
});
});
}
);

View File

@ -26,7 +26,7 @@
* @namespace platform/containment * @namespace platform/containment
*/ */
define( define(
['../../../src/api/objects/object-utils'], ['objectUtils'],
function (objectUtils) { function (objectUtils) {
function PersistableCompositionPolicy(openmct) { function PersistableCompositionPolicy(openmct) {

View File

@ -81,7 +81,7 @@ define(
baseContext = context || {}; baseContext = context || {};
} }
var actionContext = _.extend({}, baseContext); var actionContext = Object.assign({}, baseContext);
actionContext.domainObject = this.domainObject; actionContext.domainObject = this.domainObject;
return this.actionService.getActions(actionContext); return this.actionService.getActions(actionContext);

View File

@ -121,7 +121,7 @@ define(['lodash'], function (_) {
*/ */
ExportAsJSONAction.prototype.rewriteLink = function (child, parent) { ExportAsJSONAction.prototype.rewriteLink = function (child, parent) {
this.externalIdentifiers.push(this.getId(child)); this.externalIdentifiers.push(this.getId(child));
var index = _.findIndex(parent.composition, function (id) { var index = parent.composition.findIndex(id => {
return _.isEqual(child.identifier, id); return _.isEqual(child.identifier, id);
}); });
var copyOfChild = this.copyObject(child); var copyOfChild = this.copyObject(child);

View File

@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['zepto', '../../../../src/api/objects/object-utils.js'], function ($, objectUtils) { define(['zepto', 'objectUtils'], function ($, objectUtils) {
/** /**
* The ImportAsJSONAction is available from context menus and allows a user * The ImportAsJSONAction is available from context menus and allows a user

View File

@ -25,7 +25,7 @@
* Module defining GenericSearchProvider. Created by shale on 07/16/2015. * Module defining GenericSearchProvider. Created by shale on 07/16/2015.
*/ */
define([ define([
'../../../../src/api/objects/object-utils', 'objectUtils',
'lodash' 'lodash'
], function ( ], function (
objectUtils, objectUtils,
@ -191,7 +191,7 @@ define([
} }
var domainObject = objectUtils.toNewFormat(model, id); var domainObject = objectUtils.toNewFormat(model, id);
var composition = _.find(this.openmct.composition.registry, function (p) { var composition = this.openmct.composition.registry.find(p => {
return p.appliesTo(domainObject); return p.appliesTo(domainObject);
}); });

View File

@ -25,7 +25,7 @@
*/ */
define( define(
[ [
'../../../src/api/objects/object-utils', 'objectUtils',
'lodash' 'lodash'
], ],
function ( function (
@ -235,7 +235,7 @@ define(
var defaultRange = metadata.valuesForHints(['range'])[0]; var defaultRange = metadata.valuesForHints(['range'])[0];
defaultRange = defaultRange ? defaultRange.key : undefined; defaultRange = defaultRange ? defaultRange.key : undefined;
var sourceMap = _.indexBy(metadata.values(), 'key'); var sourceMap = _.keyBy(metadata.values(), 'key');
var isLegacyProvider = telemetryAPI.findRequestProvider(domainObject) === var isLegacyProvider = telemetryAPI.findRequestProvider(domainObject) ===
telemetryAPI.legacyProvider; telemetryAPI.legacyProvider;
@ -300,7 +300,7 @@ define(
var defaultRange = metadata.valuesForHints(['range'])[0]; var defaultRange = metadata.valuesForHints(['range'])[0];
defaultRange = defaultRange ? defaultRange.key : undefined; defaultRange = defaultRange ? defaultRange.key : undefined;
var sourceMap = _.indexBy(metadata.values(), 'key'); var sourceMap = _.keyBy(metadata.values(), 'key');
var isLegacyProvider = telemetryAPI.findSubscriptionProvider(domainObject) === var isLegacyProvider = telemetryAPI.findSubscriptionProvider(domainObject) ===
telemetryAPI.legacyProvider; telemetryAPI.legacyProvider;

View File

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

View File

@ -28,7 +28,7 @@ define([
'./api/api', './api/api',
'./api/overlays/OverlayAPI', './api/overlays/OverlayAPI',
'./selection/Selection', './selection/Selection',
'./api/objects/object-utils', 'objectUtils',
'./plugins/plugins', './plugins/plugins',
'./adapter/indicators/legacy-indicators-plugin', './adapter/indicators/legacy-indicators-plugin',
'./plugins/buildInfo/plugin', './plugins/buildInfo/plugin',
@ -249,7 +249,7 @@ define([
this.legacyRegistry = new BundleRegistry(); this.legacyRegistry = new BundleRegistry();
installDefaultBundles(this.legacyRegistry); installDefaultBundles(this.legacyRegistry);
// Plugin's that are installed by default // Plugins that are installed by default
this.install(this.plugins.Plot()); this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable()); this.install(this.plugins.TelemetryTable());
@ -266,6 +266,7 @@ define([
this.install(this.plugins.WebPage()); this.install(this.plugins.WebPage());
this.install(this.plugins.Condition()); this.install(this.plugins.Condition());
this.install(this.plugins.ConditionWidget()); this.install(this.plugins.ConditionWidget());
this.install(this.plugins.NotificationIndicator());
} }
MCT.prototype = Object.create(EventEmitter.prototype); MCT.prototype = Object.create(EventEmitter.prototype);
@ -350,17 +351,13 @@ define([
* @param {HTMLElement} [domElement] the DOM element in which to run * @param {HTMLElement} [domElement] the DOM element in which to run
* MCT; if undefined, MCT will be run in the body of the document * MCT; if undefined, MCT will be run in the body of the document
*/ */
MCT.prototype.start = function (domElement) { MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
if (!this.plugins.DisplayLayout._installed) { if (!this.plugins.DisplayLayout._installed) {
this.install(this.plugins.DisplayLayout({ this.install(this.plugins.DisplayLayout({
showAsView: ['summary-widget'] showAsView: ['summary-widget']
})); }));
} }
if (!domElement) {
domElement = document.body;
}
this.element = domElement; this.element = domElement;
this.legacyExtension('runs', { this.legacyExtension('runs', {
@ -400,24 +397,31 @@ define([
// something has depended upon objectService. Cool, right? // something has depended upon objectService. Cool, right?
this.$injector.get('objectService'); this.$injector.get('objectService');
var appLayout = new Vue({ if (!isHeadlessMode) {
components: { var appLayout = new Vue({
'Layout': Layout.default components: {
}, 'Layout': Layout.default
provide: { },
openmct: this provide: {
}, openmct: this
template: '<Layout ref="layout"></Layout>' },
}); template: '<Layout ref="layout"></Layout>'
domElement.appendChild(appLayout.$mount().$el); });
domElement.appendChild(appLayout.$mount().$el);
this.layout = appLayout.$refs.layout; this.layout = appLayout.$refs.layout;
Browse(this); Browse(this);
}
this.router.start(); this.router.start();
this.emit('start'); this.emit('start');
}.bind(this)); }.bind(this));
}; };
MCT.prototype.startHeadless = function () {
let unreachableNode = document.createElement('div');
return this.start(unreachableNode, true);
}
/** /**
* Install a plugin in MCT. * Install a plugin in MCT.
* *

View File

@ -21,11 +21,11 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'./MCT',
'./plugins/plugins', './plugins/plugins',
'legacyRegistry' 'legacyRegistry',
], function (MCT, plugins, legacyRegistry) { 'testUtils'
xdescribe("MCT", function () { ], function (plugins, legacyRegistry, testUtils) {
describe("MCT", function () {
var openmct; var openmct;
var mockPlugin; var mockPlugin;
var mockPlugin2; var mockPlugin2;
@ -38,7 +38,7 @@ define([
mockListener = jasmine.createSpy('listener'); mockListener = jasmine.createSpy('listener');
oldBundles = legacyRegistry.list(); oldBundles = legacyRegistry.list();
openmct = new MCT(); openmct = testUtils.createOpenMct();
openmct.install(mockPlugin); openmct.install(mockPlugin);
openmct.install(mockPlugin2); openmct.install(mockPlugin2);
@ -63,8 +63,11 @@ define([
}); });
describe("start", function () { describe("start", function () {
beforeEach(function () { let appHolder;
openmct.start(); beforeEach(function (done) {
appHolder = document.createElement("div");
openmct.on('start', done);
openmct.start(appHolder);
}); });
it("calls plugins for configuration", function () { it("calls plugins for configuration", function () {
@ -75,25 +78,51 @@ define([
it("emits a start event", function () { it("emits a start event", function () {
expect(mockListener).toHaveBeenCalled(); expect(mockListener).toHaveBeenCalled();
}); });
it("Renders the application into the provided container element", function () {
let openMctShellElements = appHolder.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(1);
});
});
describe("startHeadless", function () {
beforeEach(function (done) {
openmct.on('start', done);
openmct.startHeadless();
});
it("calls plugins for configuration", function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it("emits a start event", function () {
expect(mockListener).toHaveBeenCalled();
});
it("Does not render Open MCT", function () {
let openMctShellElements = document.body.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(0);
});
}); });
describe("setAssetPath", function () { describe("setAssetPath", function () {
var testAssetPath; var testAssetPath;
beforeEach(function () { beforeEach(function () {
testAssetPath = "some/path";
openmct.legacyExtension = jasmine.createSpy('legacyExtension'); openmct.legacyExtension = jasmine.createSpy('legacyExtension');
openmct.setAssetPath(testAssetPath);
}); });
it("internally configures the path for assets", function () { it("configures the path for assets", function () {
expect(openmct.legacyExtension).toHaveBeenCalledWith( testAssetPath = "some/path/";
'constants', openmct.setAssetPath(testAssetPath);
{ expect(openmct.getAssetPath()).toBe(testAssetPath);
key: "ASSETS_PATH", });
value: testAssetPath
} it("adds a trailing /", function () {
); testAssetPath = "some/path";
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath + "/");
}); });
}); });
}); });

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'../../api/objects/object-utils' 'objectUtils'
], function (objectUtils) { ], function (objectUtils) {
function ActionDialogDecorator(mct, actionService) { function ActionDialogDecorator(mct, actionService) {
this.mct = mct; this.mct = mct;

View File

@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
define(['../../api/objects/object-utils'], function (objectUtils) { define(['objectUtils'], function (objectUtils) {
function AdapterCapability(domainObject) { function AdapterCapability(domainObject) {
this.domainObject = domainObject; this.domainObject = domainObject;
} }

View File

@ -24,7 +24,7 @@
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14. * Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
*/ */
define([ define([
'../../api/objects/object-utils', 'objectUtils',
'../../../platform/core/src/capabilities/ContextualDomainObject' '../../../platform/core/src/capabilities/ContextualDomainObject'
], function (objectUtils, ContextualDomainObject) { ], function (objectUtils, ContextualDomainObject) {
function AlternateCompositionCapability($injector, domainObject) { function AlternateCompositionCapability($injector, domainObject) {

View File

@ -31,6 +31,7 @@ define([
var capability = viewConstructor(domainObject); var capability = viewConstructor(domainObject);
var oldInvoke = capability.invoke.bind(capability); var oldInvoke = capability.invoke.bind(capability);
/* eslint-disable you-dont-need-lodash-underscore/map */
capability.invoke = function () { capability.invoke = function () {
var availableViews = oldInvoke(); var availableViews = oldInvoke();
var newDomainObject = capability var newDomainObject = capability
@ -52,6 +53,8 @@ define([
.map('view') .map('view')
.value(); .value();
}; };
/* eslint-enable you-dont-need-lodash-underscore/map */
return capability; return capability;
}; };
} }

View File

@ -22,7 +22,7 @@
define([ define([
'../capabilities/AlternateCompositionCapability', '../capabilities/AlternateCompositionCapability',
'../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
AlternateCompositionCapability, AlternateCompositionCapability,
objectUtils objectUtils

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
utils utils
) { ) {

View File

@ -78,7 +78,7 @@ define([
}; };
TimeSettingsURLHandler.prototype.parseQueryParams = function () { TimeSettingsURLHandler.prototype.parseQueryParams = function () {
var searchParams = _.pick(this.$location.search(), _.values(SEARCH)); var searchParams = _.pick(this.$location.search(), Object.values(SEARCH));
var parsedParams = { var parsedParams = {
clock: searchParams[SEARCH.MODE], clock: searchParams[SEARCH.MODE],
timeSystem: searchParams[SEARCH.TIME_SYSTEM] timeSystem: searchParams[SEARCH.TIME_SYSTEM]

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
utils utils
) { ) {

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
objectUtils objectUtils
) { ) {

View File

@ -1,7 +1,7 @@
define([ define([
'./LegacyViewProvider', './LegacyViewProvider',
'./TypeInspectorViewProvider', './TypeInspectorViewProvider',
'../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
LegacyViewProvider, LegacyViewProvider,
TypeInspectorViewProvider, TypeInspectorViewProvider,

View File

@ -70,7 +70,7 @@ define([
* @memberof module:openmct.CompositionAPI# * @memberof module:openmct.CompositionAPI#
*/ */
CompositionAPI.prototype.get = function (domainObject) { CompositionAPI.prototype.get = function (domainObject) {
var provider = _.find(this.registry, function (p) { var provider = this.registry.find(p => {
return p.appliesTo(domainObject); return p.appliesTo(domainObject);
}); });

View File

@ -122,7 +122,7 @@ define([
throw new Error('Event not supported by composition: ' + event); throw new Error('Event not supported by composition: ' + event);
} }
var index = _.findIndex(this.listeners[event], function (l) { var index = this.listeners[event].findIndex(l => {
return l.callback === callback && l.context === context; return l.callback === callback && l.context === context;
}); });

View File

@ -22,7 +22,7 @@
define([ define([
'lodash', 'lodash',
'../objects/object-utils' 'objectUtils'
], function ( ], function (
_, _,
objectUtils objectUtils
@ -143,7 +143,7 @@ define([
var keyString = objectUtils.makeKeyString(domainObject.identifier); var keyString = objectUtils.makeKeyString(domainObject.identifier);
var objectListeners = this.listeningTo[keyString]; var objectListeners = this.listeningTo[keyString];
var index = _.findIndex(objectListeners[event], function (l) { var index = objectListeners[event].findIndex(l => {
return l.callback === callback && l.context === context; return l.callback === callback && l.context === context;
}); });
@ -196,8 +196,8 @@ define([
* @private * @private
*/ */
DefaultCompositionProvider.prototype.includes = function (parent, childId) { DefaultCompositionProvider.prototype.includes = function (parent, childId) {
return parent.composition.findIndex(composee => return parent.composition.some(composee =>
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1; this.publicAPI.objects.areIdsEqual(composee, childId));
}; };
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) { DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {

View File

@ -128,6 +128,11 @@ export default class NotificationAPI extends EventEmitter {
return this._notify(notificationModel); return this._notify(notificationModel);
} }
dismissAllNotifications() {
this.notifications = [];
this.emit('dismiss-all');
}
/** /**
* Minimize a notification. The notification will still be available * Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a * from the notification list. Typically notifications with a

View File

@ -21,7 +21,7 @@
*****************************************************************************/ *****************************************************************************/
define([ define([
'./object-utils.js', 'objectUtils',
'lodash' 'lodash'
], function ( ], function (
utils, utils,

View File

@ -22,7 +22,7 @@
define([ define([
'lodash', 'lodash',
'./object-utils', 'objectUtils',
'./MutableObject', './MutableObject',
'./RootRegistry', './RootRegistry',
'./RootObjectProvider', './RootObjectProvider',

View File

@ -43,7 +43,7 @@ define([
} }
RootRegistry.prototype.addRoot = function (key) { RootRegistry.prototype.addRoot = function (key) {
if (isKey(key) || (_.isArray(key) && _.every(key, isKey))) { if (isKey(key) || (Array.isArray(key) && key.every(isKey))) {
this.providers.push(function () { this.providers.push(function () {
return key; return key;
}); });

View File

@ -1,5 +1,5 @@
define([ define([
'../object-utils' 'objectUtils'
], function ( ], function (
objectUtils objectUtils
) { ) {

View File

@ -85,9 +85,9 @@ define([
value: +e.value value: +e.value
}; };
}), 'e.value'); }), 'e.value');
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value'); valueMetadata.values = valueMetadata.enumerations.map(e => e.value);
valueMetadata.max = _.max(valueMetadata.values); valueMetadata.max = Math.max(valueMetadata.values);
valueMetadata.min = _.min(valueMetadata.values); valueMetadata.min = Math.min(valueMetadata.values);
} }
valueMetadatas.push(valueMetadata); valueMetadatas.push(valueMetadata);
@ -103,7 +103,7 @@ define([
var metadata = domainObject.telemetry || {}; var metadata = domainObject.telemetry || {};
if (this.typeHasTelemetry(domainObject)) { if (this.typeHasTelemetry(domainObject)) {
var typeMetadata = this.typeService.getType(domainObject.type).typeDef.telemetry; var typeMetadata = this.typeService.getType(domainObject.type).typeDef.telemetry;
_.extend(metadata, typeMetadata); Object.assign(metadata, typeMetadata);
if (!metadata.values) { if (!metadata.values) {
metadata.values = valueMetadatasFromOldFormat(metadata); metadata.values = valueMetadatasFromOldFormat(metadata);
} }

View File

@ -24,7 +24,7 @@ define([
'./TelemetryMetadataManager', './TelemetryMetadataManager',
'./TelemetryValueFormatter', './TelemetryValueFormatter',
'./DefaultMetadataProvider', './DefaultMetadataProvider',
'../objects/object-utils', 'objectUtils',
'lodash' 'lodash'
], function ( ], function (
TelemetryMetadataManager, TelemetryMetadataManager,
@ -370,7 +370,7 @@ define([
TelemetryAPI.prototype.commonValuesForHints = function (metadatas, hints) { TelemetryAPI.prototype.commonValuesForHints = function (metadatas, hints) {
var options = metadatas.map(function (metadata) { var options = metadatas.map(function (metadata) {
var values = metadata.valuesForHints(hints); var values = metadata.valuesForHints(hints);
return _.indexBy(values, 'key'); return _.keyBy(values, 'key');
}).reduce(function (a, b) { }).reduce(function (a, b) {
var results = {}; var results = {};
Object.keys(a).forEach(function (key) { Object.keys(a).forEach(function (key) {
@ -383,7 +383,7 @@ define([
var sortKeys = hints.map(function (h) { var sortKeys = hints.map(function (h) {
return 'hints.' + h; return 'hints.' + h;
}); });
return _.sortByAll(options, sortKeys); return _.sortBy(options, sortKeys);
}; };
/** /**

View File

@ -57,13 +57,13 @@ define([
if (valueMetadata.format === 'enum') { if (valueMetadata.format === 'enum') {
if (!valueMetadata.values) { if (!valueMetadata.values) {
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value'); valueMetadata.values = valueMetadata.enumerations.map(e => e.value);
} }
if (!valueMetadata.hasOwnProperty('max')) { if (!valueMetadata.hasOwnProperty('max')) {
valueMetadata.max = _.max(valueMetadata.values) + 1; valueMetadata.max = Math.max(valueMetadata.values) + 1;
} }
if (!valueMetadata.hasOwnProperty('min')) { if (!valueMetadata.hasOwnProperty('min')) {
valueMetadata.min = _.min(valueMetadata.values) - 1; valueMetadata.min = Math.min(valueMetadata.values) - 1;
} }
} }
@ -121,7 +121,7 @@ define([
return metadata.hints[hint]; return metadata.hints[hint];
} }
}); });
return _.sortByAll(matchingMetadata, ...iteratees); return _.sortBy(matchingMetadata, ...iteratees);
}; };
TelemetryMetadataManager.prototype.getFilterableValues = function () { TelemetryMetadataManager.prototype.getFilterableValues = function () {

View File

@ -75,7 +75,7 @@ export default {
this.items.push(item); this.items.push(item);
}, },
removeItem(identifier) { removeItem(identifier) {
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key); let index = this.items.findIndex(item => this.openmct.objects.makeKeyString(identifier) === item.key);
this.items.splice(index, 1); this.items.splice(index, 1);
}, },

View File

@ -102,7 +102,7 @@ export default {
this.compositions.push({composition, addCallback, removeCallback}); this.compositions.push({composition, addCallback, removeCallback});
}, },
removePrimary(identifier) { removePrimary(identifier) {
let index = _.findIndex(this.primaryTelemetryObjects, (primary) => this.openmct.objects.makeKeyString(identifier) === primary.key), let index = this.primaryTelemetryObjects.findIndex(primary => this.openmct.objects.makeKeyString(identifier) === primary.key),
primary = this.primaryTelemetryObjects[index]; primary = this.primaryTelemetryObjects[index];
this.$set(this.secondaryTelemetryObjects, primary.key, undefined); this.$set(this.secondaryTelemetryObjects, primary.key, undefined);
@ -130,7 +130,7 @@ export default {
removeSecondary(primary) { removeSecondary(primary) {
return (identifier) => { return (identifier) => {
let array = this.secondaryTelemetryObjects[primary.key], let array = this.secondaryTelemetryObjects[primary.key],
index = _.findIndex(array, (secondary) => this.openmct.objects.makeKeyString(identifier) === secondary.key); index = array.findIndex(secondary => this.openmct.objects.makeKeyString(identifier) === secondary.key);
array.splice(index, 1); array.splice(index, 1);

View File

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

View File

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

View File

@ -29,6 +29,7 @@ export default class StyleRuleManager extends EventEmitter {
this.callback = callback; this.callback = callback;
if (suppressSubscriptionOnEdit) { if (suppressSubscriptionOnEdit) {
this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this)); this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
this.isEditing = this.openmct.editor.editing;
} }
if (styleConfiguration) { if (styleConfiguration) {
this.initialize(styleConfiguration); this.initialize(styleConfiguration);
@ -156,8 +157,6 @@ export default class StyleRuleManager extends EventEmitter {
} }
delete this.stopProvidingTelemetry; delete this.stopProvidingTelemetry;
this.conditionSetIdentifier = undefined; this.conditionSetIdentifier = undefined;
this.isEditing = undefined;
this.callback = undefined;
} }
} }

View File

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

View File

@ -58,6 +58,7 @@
<Condition v-for="(condition, index) in conditionCollection" <Condition v-for="(condition, index) in conditionCollection"
:key="condition.id" :key="condition.id"
:condition="condition" :condition="condition"
:current-condition-id="currentConditionId"
:condition-index="index" :condition-index="index"
:telemetry="telemetryObjs" :telemetry="telemetryObjs"
:is-editing="isEditing" :is-editing="isEditing"
@ -107,7 +108,8 @@ export default {
moveIndex: undefined, moveIndex: undefined,
isDragging: false, isDragging: false,
defaultOutput: undefined, defaultOutput: undefined,
dragCounter: 0 dragCounter: 0,
currentConditionId: ''
}; };
}, },
watch: { watch: {
@ -145,6 +147,7 @@ export default {
}, },
methods: { methods: {
handleConditionSetResultUpdated(data) { handleConditionSetResultUpdated(data) {
this.currentConditionId = data.conditionId;
this.$emit('conditionSetResultUpdated', data) this.$emit('conditionSetResultUpdated', data)
}, },
observeForChanges() { observeForChanges() {
@ -197,7 +200,7 @@ export default {
this.$emit('telemetryUpdated', this.telemetryObjs); this.$emit('telemetryUpdated', this.telemetryObjs);
}, },
removeTelemetryObject(identifier) { removeTelemetryObject(identifier) {
let index = _.findIndex(this.telemetryObjs, (obj) => { let index = this.telemetryObjs.findIndex(obj => {
let objId = this.openmct.objects.makeKeyString(obj.identifier); let objId = this.openmct.objects.makeKeyString(obj.identifier);
let id = this.openmct.objects.makeKeyString(identifier); let id = this.openmct.objects.makeKeyString(identifier);
return objId === id; return objId === id;

View File

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

View File

@ -108,6 +108,7 @@ import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import Vue from 'vue'; import Vue from 'vue';
import PreviewAction from "@/ui/preview/PreviewAction.js"; import PreviewAction from "@/ui/preview/PreviewAction.js";
import {getApplicableStylesForItem} from "@/plugins/condition/utils/styleUtils"; import {getApplicableStylesForItem} from "@/plugins/condition/utils/styleUtils";
import isEmpty from 'lodash/isEmpty';
export default { export default {
name: 'ConditionalStylesView', name: 'ConditionalStylesView',
@ -288,7 +289,7 @@ export default {
delete domainObjectStyles[this.itemId].conditionSetIdentifier; delete domainObjectStyles[this.itemId].conditionSetIdentifier;
domainObjectStyles[this.itemId].styles = undefined; domainObjectStyles[this.itemId].styles = undefined;
delete domainObjectStyles[this.itemId].styles; delete domainObjectStyles[this.itemId].styles;
if (_.isEmpty(domainObjectStyles[this.itemId])) { if (isEmpty(domainObjectStyles[this.itemId])) {
delete domainObjectStyles[this.itemId]; delete domainObjectStyles[this.itemId];
} }
} else { } else {
@ -299,7 +300,7 @@ export default {
domainObjectStyles.styles = undefined; domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles; delete domainObjectStyles.styles;
} }
if (_.isEmpty(domainObjectStyles)) { if (isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined; domainObjectStyles = undefined;
} }
@ -337,7 +338,7 @@ export default {
delete domainObjectStyles[this.itemId]; delete domainObjectStyles[this.itemId];
} }
}); });
if (_.isEmpty(domainObjectStyles)) { if (isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined; domainObjectStyles = undefined;
} }
this.persist(domainObjectStyles); this.persist(domainObjectStyles);

View File

@ -50,6 +50,7 @@
import StyleEditor from "./StyleEditor.vue"; import StyleEditor from "./StyleEditor.vue";
import PreviewAction from "@/ui/preview/PreviewAction.js"; import PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionalStyleForItem } from "@/plugins/condition/utils/styleUtils"; import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionalStyleForItem } from "@/plugins/condition/utils/styleUtils";
import isEmpty from 'lodash/isEmpty';
export default { export default {
name: 'MultiSelectStylesView', name: 'MultiSelectStylesView',
@ -178,7 +179,7 @@ export default {
domainObjectStyles[itemId] = undefined; domainObjectStyles[itemId] = undefined;
delete domainObjectStyles[this.itemId]; delete domainObjectStyles[this.itemId];
if (_.isEmpty(domainObjectStyles)) { if (isEmpty(domainObjectStyles)) {
domainObjectStyles = undefined; domainObjectStyles = undefined;
} }
this.persist(this.domainObject, domainObjectStyles); this.persist(this.domainObject, domainObjectStyles);
@ -239,7 +240,7 @@ export default {
if (this.isStaticAndConditionalStyles) { if (this.isStaticAndConditionalStyles) {
this.removeConditionalStyles(domainObjectStyles, item.id); this.removeConditionalStyles(domainObjectStyles, item.id);
} }
if (_.isEmpty(itemStaticStyle)) { if (isEmpty(itemStaticStyle)) {
itemStaticStyle = undefined; itemStaticStyle = undefined;
domainObjectStyles[item.id] = undefined; domainObjectStyles[item.id] = undefined;
} else { } else {

View File

@ -0,0 +1,601 @@
/*****************************************************************************
* 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 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>
<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"
:mixed-styles="mixedStyles"
@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 PreviewAction from "@/ui/preview/PreviewAction.js";
import { getApplicableStylesForItem, getConsolidatedStyleValues, getConditionSetIdentifierForItem } from "@/plugins/condition/utils/styleUtils";
import ConditionSetSelectorDialog from "@/plugins/condition/components/inspector/ConditionSetSelectorDialog.vue";
import ConditionError from "@/plugins/condition/components/ConditionError.vue";
import ConditionDescription from "@/plugins/condition/components/ConditionDescription.vue";
import Vue from 'vue';
export default {
name: 'StylesView',
components: {
StyleEditor,
ConditionError,
ConditionDescription
},
inject: [
'openmct',
'selection'
],
data() {
return {
staticStyle: undefined,
isEditing: this.openmct.editor.isEditing(),
mixedStyles: [],
isStaticAndConditionalStyles: false,
conditionalStyles: [],
conditionSetDomainObject: undefined,
conditions: undefined,
conditionsLoaded: false,
navigateToPath: '',
selectedConditionId: ''
}
},
destroyed() {
this.removeListeners();
},
mounted() {
this.items = [];
this.previewAction = new PreviewAction(this.openmct);
this.isMultipleSelection = this.selection.length > 1;
this.getObjectsAndItemsFromSelection();
if (!this.isMultipleSelection) {
let objectStyles = this.getObjectStyles();
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: {
getObjectStyles() {
let objectStyles;
if (this.domainObjectsById) {
const domainObject = Object.values(this.domainObjectsById)[0];
if (domainObject.configuration && domainObject.configuration.objectStyles) {
objectStyles = domainObject.configuration.objectStyles;
}
} else if (this.items.length) {
const itemId = this.items[0].id;
if (this.domainObject.configuration && this.domainObject.configuration.objectStyles && this.domainObject.configuration.objectStyles[itemId]) {
objectStyles = this.domainObject.configuration.objectStyles[itemId];
}
} else if (this.domainObject.configuration && this.domainObject.configuration.objectStyles) {
objectStyles = this.domainObject.configuration.objectStyles;
}
return objectStyles;
},
setEditState(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
} else {
this.subscribeToConditionSet();
}
},
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);
}
},
isItemType(type, item) {
return item && (item.type === type);
},
hasConditionalStyle(domainObject, layoutItem) {
const id = layoutItem ? layoutItem.id : undefined;
return getConditionSetIdentifierForItem(domainObject, id) !== undefined;
},
getObjectsAndItemsFromSelection() {
let domainObject;
let subObjects = [];
let itemsWithConditionalStyles = 0;
//multiple selection
let itemInitialStyles = [];
let itemStyle;
this.selection.forEach((selectionItem) => {
const item = selectionItem[0].context.item;
const layoutItem = selectionItem[0].context.layoutItem;
const isChildItem = selectionItem.length > 1;
if (!isChildItem) {
domainObject = item;
itemStyle = getApplicableStylesForItem(item);
if (this.hasConditionalStyle(item)) {
itemsWithConditionalStyles += 1;
}
} else {
this.canHide = true;
domainObject = selectionItem[1].context.item;
if (item && !layoutItem || this.isItemType('subobject-view', layoutItem)) {
subObjects.push(item);
itemStyle = getApplicableStylesForItem(item);
if (this.hasConditionalStyle(item)) {
itemsWithConditionalStyles += 1;
}
} else {
itemStyle = getApplicableStylesForItem(domainObject, layoutItem || item);
this.items.push({
id: layoutItem.id,
applicableStyles: itemStyle
});
if (this.hasConditionalStyle(item, layoutItem)) {
itemsWithConditionalStyles += 1;
}
}
}
itemInitialStyles.push(itemStyle);
});
this.isStaticAndConditionalStyles = this.isMultipleSelection && itemsWithConditionalStyles;
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) {
let keys = Object.keys(this.domainObject.configuration.objectStyles || {});
keys.forEach((key) => {
if (this.isKeyItemId(key)) {
if (!(newItems.find(item => item.id === key))) {
this.removeItemStyles(key);
}
}
});
},
isKeyItemId(key) {
return (key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'defaultConditionId') &&
(key !== 'selectedConditionId') &&
(key !== 'conditionSetIdentifier');
},
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, '*', (newObject) => {
this.domainObjectsById[id] = JSON.parse(JSON.stringify(newObject));
});
this.unObserveObjects.push(unobserveObject);
},
removeListeners() {
if (this.stopObserving) {
this.stopObserving();
}
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
if (this.unObserveObjects) {
this.unObserveObjects.forEach((unObserveObject) => {
unObserveObject();
});
}
this.unObserveObjects = [];
},
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 : '';
},
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();
},
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.getAndPersistStyles(null, this.selectedConditionId);
if (!this.isEditing) {
this.subscribeToConditionSet();
}
},
//TODO: Double check how this works for single styles
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)
};
}
},
removeItemStyles(itemId) {
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (itemId && domainObjectStyles[itemId]) {
delete domainObjectStyles[itemId];
if (Object.keys(domainObjectStyles).length <= 0) {
domainObjectStyles = undefined;
}
this.persist(this.domainObject, domainObjectStyles);
}
},
findStyleByConditionId(id) {
return this.conditionalStyles.find(conditionalStyle => conditionalStyle.conditionId === id);
},
getCondition(id) {
return this.conditions ? this.conditions[id] : {};
},
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()
});
},
removeConditionSet() {
this.conditionSetDomainObject = undefined;
this.conditionalStyles = [];
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.domainObjectsById) {
const domainObjects = Object.values(this.domainObjectsById);
domainObjects.forEach(domainObject => {
let objectStyles = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
this.removeConditionalStyles(objectStyles);
if (Object.keys(objectStyles).length <= 0) {
objectStyles = undefined;
}
this.persist(domainObject, objectStyles);
});
}
if (this.items.length) {
this.items.forEach((item) => {
const itemId = item.id;
this.removeConditionalStyles(domainObjectStyles, itemId);
if (Object.keys(domainObjectStyles[itemId]).length <= 0) {
delete domainObjectStyles[itemId];
}
});
} else {
this.removeConditionalStyles(domainObjectStyles);
}
if (Object.keys(domainObjectStyles).length <= 0) {
domainObjectStyles = undefined;
}
this.persist(this.domainObject, domainObjectStyles);
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
removeConditionalStyles(domainObjectStyles, itemId) {
if (itemId && domainObjectStyles[itemId]) {
domainObjectStyles[itemId].conditionSetIdentifier = undefined;
delete domainObjectStyles[itemId].conditionSetIdentifier;
domainObjectStyles[itemId].selectedConditionId = undefined;
domainObjectStyles[itemId].defaultConditionId = undefined;
domainObjectStyles[itemId].styles = undefined;
delete domainObjectStyles[itemId].styles;
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.selectedConditionId = undefined;
domainObjectStyles.defaultConditionId = undefined;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
}
},
updateStaticStyle(staticStyle, property) {
//update the static style for each of the layoutItems as well as each sub object item
this.staticStyle = staticStyle;
this.removeConditionSet();
this.getAndPersistStyles(property);
},
updateConditionalStyle(conditionStyle, property) {
let foundStyle = this.findStyleByConditionId(conditionStyle.conditionId);
if (foundStyle) {
foundStyle.style = conditionStyle.style;
this.selectedConditionId = foundStyle.conditionId;
this.getAndPersistStyles(property);
}
},
getAndPersistStyles(property, defaultConditionId) {
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, this.items, defaultConditionId));
if (this.domainObjectsById) {
const domainObjects = Object.values(this.domainObjectsById);
domainObjects.forEach(domainObject => {
this.persist(domainObject, this.getDomainObjectStyle(domainObject, property, null, defaultConditionId));
});
}
if (!this.items.length && !this.domainObjectsById) {
this.persist(this.domainObject, this.getDomainObjectStyle(this.domainObject, property, null, defaultConditionId));
}
this.isStaticAndConditionalStyles = false;
if (property) {
let foundIndex = this.mixedStyles.indexOf(property);
if (foundIndex > -1) {
this.mixedStyles.splice(foundIndex, 1);
}
}
},
getDomainObjectStyle(domainObject, property, items, 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 = (domainObject.configuration && domainObject.configuration.objectStyles) || {};
if (items) {
items.forEach(item => {
let itemStaticStyle = {};
let itemConditionalStyle = { styles: []};
if (!this.conditionSetDomainObject) {
if (domainObjectStyles[item.id] && domainObjectStyles[item.id].staticStyle) {
itemStaticStyle = domainObjectStyles[item.id].staticStyle.style;
}
if (item.applicableStyles[property]) {
itemStaticStyle[property] = this.staticStyle.style[property];
}
if (Object.keys(itemStaticStyle).length <= 0) {
itemStaticStyle = undefined;
}
domainObjectStyles[item.id] = { staticStyle: { style: itemStaticStyle } };
} else {
objectStyle.styles.forEach((conditionalStyle, index) => {
let style = {};
Object.keys(item.applicableStyles).concat(['isStyleInvisible']).forEach(key => {
style[key] = conditionalStyle.style[key];
});
itemConditionalStyle.styles.push({
...conditionalStyle,
style
});
});
domainObjectStyles[item.id] = {
...domainObjectStyles[item.id],
...objectStyle,
...itemConditionalStyle
};
}
});
} else {
domainObjectStyles = {
...domainObjectStyles,
...objectStyle
};
}
return domainObjectStyles;
},
applySelectedConditionStyle(conditionId) {
this.selectedConditionId = conditionId;
this.getAndPersistStyles();
},
persist(domainObject, style) {
this.openmct.objects.mutate(domainObject, 'configuration.objectStyles', style);
}
}
}
</script>

View File

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

View File

@ -20,25 +20,21 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import { createOpenMct } from "testTools"; import { createOpenMct } from "testUtils";
import ConditionPlugin from "./plugin"; import ConditionPlugin from "./plugin";
let openmct = createOpenMct();
openmct.install(new ConditionPlugin());
let conditionSetDefinition;
let mockConditionSetDomainObject;
let element;
let child;
describe('the plugin', function () { describe('the plugin', function () {
let conditionSetDefinition;
let mockConditionSetDomainObject;
let element;
let child;
let openmct;
beforeAll((done) => { beforeAll((done) => {
openmct = createOpenMct();
openmct.install(new ConditionPlugin());
conditionSetDefinition = openmct.types.get('conditionSet').definition; conditionSetDefinition = openmct.types.get('conditionSet').definition;
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
element = document.createElement('div'); element = document.createElement('div');
child = document.createElement('div'); child = document.createElement('div');
@ -55,7 +51,7 @@ describe('the plugin', function () {
conditionSetDefinition.initialize(mockConditionSetDomainObject); conditionSetDefinition.initialize(mockConditionSetDomainObject);
openmct.on('start', done); openmct.on('start', done);
openmct.start(appHolder); openmct.startHeadless();
}); });
let mockConditionSetObject = { let mockConditionSetObject = {

View File

@ -20,8 +20,6 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import _ from 'lodash';
const convertToNumbers = (input) => { const convertToNumbers = (input) => {
let numberInputs = []; let numberInputs = [];
input.forEach(inputValue => numberInputs.push(Number(inputValue))); input.forEach(inputValue => numberInputs.push(Number(inputValue)));
@ -257,7 +255,7 @@ export const OPERATIONS = [
const lhsValue = input[0] !== undefined ? input[0].toString() : ''; const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) { if (input[1]) {
const values = input[1].split(','); const values = input[1].split(',');
return values.find((value) => lhsValue === _.trim(value.toString())); return values.find((value) => lhsValue === value.toString().trim());
} }
return false; return false;
}, },
@ -274,7 +272,7 @@ export const OPERATIONS = [
const lhsValue = input[0] !== undefined ? input[0].toString() : ''; const lhsValue = input[0] !== undefined ? input[0].toString() : '';
if (input[1]) { if (input[1]) {
const values = input[1].split(','); const values = input[1].split(',');
const found = values.find((value) => lhsValue === _.trim(value.toString())); const found = values.find((value) => lhsValue === value.toString().trim());
return !found; return !found;
} }
return false; return false;

View File

@ -19,6 +19,8 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import isEmpty from 'lodash/isEmpty';
const NONE_VALUE = '__no_value'; const NONE_VALUE = '__no_value';
const styleProps = { const styleProps = {
@ -122,12 +124,25 @@ export const getConditionalStyleForItem = (domainObject, id) => {
if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) { if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
return domainObjectStyles[id].styles; return domainObjectStyles[id].styles;
} }
} else if (domainObjectStyles.staticStyle) { } else if (domainObjectStyles.conditionSetIdentifier) {
return domainObjectStyles.styles; return domainObjectStyles.styles;
} }
} }
}; };
export const getConditionSetIdentifierForItem = (domainObject, id) => {
let domainObjectStyles = domainObject && domainObject.configuration && domainObject.configuration.objectStyles;
if (domainObjectStyles) {
if (id) {
if (domainObjectStyles[id] && domainObjectStyles[id].conditionSetIdentifier) {
return domainObjectStyles[id].conditionSetIdentifier;
}
} else if (domainObjectStyles.conditionSetIdentifier) {
return domainObjectStyles.conditionSetIdentifier;
}
}
};
//Returns either existing static styles or uses SVG defaults if available //Returns either existing static styles or uses SVG defaults if available
export const getApplicableStylesForItem = (domainObject, item) => { export const getApplicableStylesForItem = (domainObject, item) => {
const type = item && item.type; const type = item && item.type;
@ -154,7 +169,7 @@ export const getApplicableStylesForItem = (domainObject, item) => {
}; };
export const getStylesWithoutNoneValue = (style) => { export const getStylesWithoutNoneValue = (style) => {
if (_.isEmpty(style) || !style) { if (isEmpty(style) || !style) {
return; return;
} }
let styleObj = {}; let styleObj = {};

View File

@ -68,7 +68,6 @@
<script> <script>
import uuid from 'uuid'; import uuid from 'uuid';
import SubobjectView from './SubobjectView.vue' import SubobjectView from './SubobjectView.vue'
import TelemetryView from './TelemetryView.vue' import TelemetryView from './TelemetryView.vue'
import BoxView from './BoxView.vue' import BoxView from './BoxView.vue'
@ -76,6 +75,7 @@ import TextView from './TextView.vue'
import LineView from './LineView.vue' import LineView from './LineView.vue'
import ImageView from './ImageView.vue' import ImageView from './ImageView.vue'
import EditMarquee from './EditMarquee.vue' import EditMarquee from './EditMarquee.vue'
import _ from 'lodash'
const ITEM_TYPE_VIEW_MAP = { const ITEM_TYPE_VIEW_MAP = {
'subobject-view': SubobjectView, 'subobject-view': SubobjectView,
@ -512,7 +512,7 @@ export default {
} }
}, },
updateTelemetryFormat(item, format) { updateTelemetryFormat(item, format) {
let index = _.findIndex(this.layoutItems, item); let index = this.layoutItems.findIndex(item);
item.format = format; item.format = format;
this.mutate(`configuration.items[${index}]`, item); this.mutate(`configuration.items[${index}]`, item);
} }

View File

@ -40,6 +40,7 @@
<script> <script>
import LayoutDrag from './../LayoutDrag' import LayoutDrag from './../LayoutDrag'
import _ from 'lodash'
export default { export default {
inject: ['openmct'], inject: ['openmct'],

View File

@ -62,6 +62,7 @@
<script> <script>
import conditionalStylesMixin from "../mixins/objectStyles-mixin"; import conditionalStylesMixin from "../mixins/objectStyles-mixin";
import _ from 'lodash';
const START_HANDLE_QUADRANTS = { const START_HANDLE_QUADRANTS = {
1: 'c-frame-edit__handle--sw', 1: 'c-frame-edit__handle--sw',

View File

@ -22,7 +22,7 @@
import Layout from './components/DisplayLayout.vue' import Layout from './components/DisplayLayout.vue'
import Vue from 'vue' import Vue from 'vue'
import objectUtils from '../../api/objects/object-utils.js' import objectUtils from 'objectUtils'
import DisplayLayoutType from './DisplayLayoutType.js' import DisplayLayoutType from './DisplayLayoutType.js'
import DisplayLayoutToolbar from './DisplayLayoutToolbar.js' import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'
import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js' import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js'

View File

@ -62,6 +62,7 @@
<script> <script>
import FilterField from './FilterField.vue'; import FilterField from './FilterField.vue';
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue'; import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
import isEmpty from 'lodash/isEmpty';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
@ -102,7 +103,7 @@ export default {
hasActiveFilters() { hasActiveFilters() {
// Should be true when the user has entered any filter values. // Should be true when the user has entered any filter values.
return Object.values(this.persistedFilters).some(comparator => { return Object.values(this.persistedFilters).some(comparator => {
return (typeof(comparator) === 'object' && !_.isEmpty(comparator)); return (typeof(comparator) === 'object' && !isEmpty(comparator));
}); });
} }
}, },

View File

@ -27,7 +27,8 @@
<script> <script>
import FilterObject from './FilterObject.vue'; import FilterObject from './FilterObject.vue';
import GlobalFilters from './GlobalFilters.vue' import GlobalFilters from './GlobalFilters.vue';
import _ from 'lodash';
const FILTER_VIEW_TITLE = 'Filters applied'; const FILTER_VIEW_TITLE = 'Filters applied';
const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied'; const FILTER_VIEW_TITLE_MIXED = 'Mixed filters applied';

View File

@ -64,6 +64,7 @@
<script> <script>
import compositionLoader from './composition-loader'; import compositionLoader from './composition-loader';
import ListItem from './ListItem.vue'; import ListItem from './ListItem.vue';
import _ from 'lodash';
export default { export default {
components: {ListItem}, components: {ListItem},

View File

@ -66,7 +66,6 @@ export default {
data() { data() {
return { return {
autoScroll: true, autoScroll: true,
date: '',
filters : { filters : {
brightness: 100, brightness: 100,
contrast: 100 contrast: 100
@ -78,22 +77,39 @@ export default {
imageHistory: [], imageHistory: [],
imageUrl: '', imageUrl: '',
isPaused: false, isPaused: false,
metadata: {},
requestCount: 0, requestCount: 0,
timeFormat: '' timeFormat: ''
} }
}, },
mounted() { mounted() {
// set
this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.subscribe(this.domainObject); this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.imageFormat = this.openmct.telemetry.getValueFormatter(this.metadata.valuesForHints(['image'])[0]);
// initialize
this.timeKey = this.openmct.time.timeSystem().key;
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
// listen
this.openmct.time.on('bounds', this.boundsChange);
this.openmct.time.on('timeSystem', this.timeSystemChange);
// kickoff
this.subscribe();
this.requestHistory();
}, },
updated() { updated() {
this.scrollToRight(); this.scrollToRight();
}, },
beforeDestroy() { beforeDestroy() {
this.stopListening(); if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
this.openmct.time.off('bounds', this.boundsChange);
this.openmct.time.off('timeSystem', this.timeSystemChange);
}, },
methods: { methods: {
datumMatchesMostRecent(datum) { datumIsNotValid(datum) {
if (this.imageHistory.length === 0) { if (this.imageHistory.length === 0) {
return false; return false;
} }
@ -103,7 +119,14 @@ export default {
const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]); const lastHistoryTime = this.timeFormat.format(this.imageHistory.slice(-1)[0]);
const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]); const lastHistoryURL = this.imageFormat.format(this.imageHistory.slice(-1)[0]);
return (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL); // datum is not valid if it matches the last datum in history,
// or it is before the last datum in the history
const datumTimeCheck = this.timeFormat.parse(datum);
const historyTimeCheck = this.timeFormat.parse(this.imageHistory.slice(-1)[0]);
const matchesLast = (datumTime === lastHistoryTime) && (datumURL === lastHistoryURL);
const isStale = datumTimeCheck < historyTimeCheck;
return matchesLast || isStale;
}, },
getImageUrl(datum) { getImageUrl(datum) {
return datum ? return datum ?
@ -147,21 +170,6 @@ export default {
return this.isPaused; return this.isPaused;
}, },
requestHistory(bounds) {
this.requestCount++;
this.imageHistory = [];
const requestId = this.requestCount;
this.openmct.telemetry
.request(this.domainObject, bounds)
.then((values = []) => {
if (this.requestCount > requestId) {
return Promise.resolve('Stale request');
}
values.forEach(this.updateHistory);
this.updateValues(values[values.length - 1]);
});
},
scrollToRight() { scrollToRight() {
if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) { if (this.isPaused || !this.$refs.thumbsWrapper || !this.autoScroll) {
return; return;
@ -188,40 +196,56 @@ export default {
image.selected = true; image.selected = true;
} }
}, },
stopListening() { boundsChange(bounds, isTick) {
if (this.unsubscribe) { if(!isTick) {
this.unsubscribe(); this.requestHistory();
delete this.unsubscribe;
} }
}, },
subscribe(domainObject) { requestHistory() {
this.date = '' let bounds = this.openmct.time.bounds();
this.imageUrl = ''; this.requestCount++;
this.openmct.objects.get(this.keystring) const requestId = this.requestCount;
.then((object) => { this.imageHistory = [];
const metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.openmct.telemetry
this.timeKey = this.openmct.time.timeSystem().key; .request(this.domainObject, bounds)
this.timeFormat = this.openmct.telemetry.getValueFormatter(metadata.value(this.timeKey)); .then((values = []) => {
this.imageFormat = this.openmct.telemetry.getValueFormatter(metadata.valuesForHints(['image'])[0]); if (this.requestCount === requestId) {
this.unsubscribe = this.openmct.telemetry values.forEach(this.updateHistory, false);
.subscribe(this.domainObject, (datum) => { this.updateValues(values[values.length - 1]);
this.updateHistory(datum); }
this.updateValues(datum); });
}); },
timeSystemChange(system) {
// reset timesystem dependent variables
this.timeKey = system.key;
this.timeFormat = this.openmct.telemetry.getValueFormatter(this.metadata.value(this.timeKey));
},
subscribe() {
this.unsubscribe = this.openmct.telemetry
.subscribe(this.domainObject, (datum) => {
let parsedTimestamp = this.timeFormat.parse(datum[this.timeKey]),
bounds = this.openmct.time.bounds();
this.requestHistory(this.openmct.time.bounds()); if(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
this.updateHistory(datum);
this.updateValues(datum);
}
}); });
}, },
unselectAllImages() { unselectAllImages() {
this.imageHistory.forEach(image => image.selected = false); this.imageHistory.forEach(image => image.selected = false);
}, },
updateHistory(datum) { updateHistory(datum, updateValues = true) {
if (this.datumMatchesMostRecent(datum)) { if (this.datumIsNotValid(datum)) {
return; return;
} }
const index = _.sortedIndex(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat)); const index = _.sortedIndexBy(this.imageHistory, datum, this.timeFormat.format.bind(this.timeFormat));
this.imageHistory.splice(index, 0, datum); this.imageHistory.splice(index, 0, datum);
if(updateValues) {
this.updateValues(datum);
}
}, },
updateValues(datum) { updateValues(datum) {
if (this.isPaused) { if (this.isPaused) {

View File

@ -169,11 +169,10 @@ export default {
const bounds = this.openmct.time.bounds(); const bounds = this.openmct.time.bounds();
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
&& this.embed.bounds.end !== bounds.end; || this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock(); const isFixedTimespanMode = !this.openmct.time.clock();
window.location.href = link; this.openmct.time.stopClock();
let message = ''; let message = '';
if (isTimeBoundChanged) { if (isTimeBoundChanged) {
this.openmct.time.bounds({ this.openmct.time.bounds({
@ -187,7 +186,11 @@ export default {
message = 'Time bound values changed to fixed timespan mode'; message = 'Time bound values changed to fixed timespan mode';
} }
this.openmct.notifications.alert(message); if (message.length) {
this.openmct.notifications.alert(message);
}
window.location.href = link;
}, },
formatTime(unixTime, timeFormat) { formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat); return Moment.utc(unixTime).format(timeFormat);

View File

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

View File

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

View File

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

View File

@ -0,0 +1,70 @@
<template>
<div
v-if="notifications.length > 0"
class="c-indicator c-indicator--clickable icon-bell"
:class="[severityClass]"
>
<span class="c-indicator__label">
<button @click="toggleNotificationsList(true)">
{{ notificationsCountMessage(notifications.length) }}
</button>
<button @click="dismissAllNotifications()">
Clear All
</button>
</span>
<span class="c-indicator__count">{{ notifications.length }}</span>
<notifications-list
v-if="showNotificationsOverlay"
:notifications="notifications"
@close="toggleNotificationsList"
@clear-all="dismissAllNotifications"
/>
</div>
</template>
<script>
import NotificationsList from './NotificationsList.vue';
export default {
inject: ['openmct'],
components: {
NotificationsList
},
data() {
return {
notifications: this.openmct.notifications.notifications,
highest: this.openmct.notifications.highest,
showNotificationsOverlay: false
}
},
computed: {
severityClass() {
return `s-status-${this.highest.severity}`;
}
},
mounted() {
this.openmct.notifications.on('notification', this.updateNotifications);
this.openmct.notifications.on('dismiss-all', this.updateNotifications);
},
methods: {
dismissAllNotifications() {
this.openmct.notifications.dismissAllNotifications();
},
toggleNotificationsList(flag) {
this.showNotificationsOverlay = flag;
},
updateNotifications() {
this.notifications = this.openmct.notifications.notifications;
this.highest = this.openmct.notifications.highest;
},
notificationsCountMessage(count) {
if (count > 1) {
return `${count} Notifications`;
} else {
return `${count} Notification`;
}
}
}
}
</script>

View File

@ -0,0 +1,85 @@
<template>
<div
class="c-message"
:class="'message-severity-' + notification.model.severity"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<span>{{ notification.model.timestamp }}</span>
</div>
<div class="c-ne__content">
<div class="w-message-contents">
<div class="c-message__top-bar">
<div class="c-message__title">{{ notification.model.message }}</div>
</div>
<div class="message-body">
<progress-bar
v-if="isProgressNotification"
:model="progressObject"
/>
</div>
</div>
</div>
<div class="c-overlay__button-bar">
<button
v-for="(dialogOption, index) in notification.model.options"
:key="index"
class="c-button"
@click="dialogOption.callback()"
>
{{ dialogOption.label }}
</button>
<button
v-if="notification.model.primaryOption"
class="c-button c-button--major"
@click="notification.model.primaryOption.callback()"
>
{{ notification.model.primaryOption.label }}
</button>
</div>
</div>
</div>
</template>
<script>
import ProgressBar from '../../../ui/components/ProgressBar.vue';
export default {
components: {
ProgressBar
},
props:{
notification: {
type: Object,
required: true
}
},
data() {
return {
isProgressNotification: false,
progressPerc: this.notification.model.progressPerc,
progressText: this.notification.model.progressText
}
},
computed: {
progressObject() {
return {
progressPerc: this.progressPerc,
progressText: this.progressText
}
}
},
mounted() {
if (this.notification.model.progressPerc) {
this.isProgressNotification = true;
this.notification.on('progress', this.updateProgressBar)
}
},
methods: {
updateProgressBar(progressPerc, progressText) {
this.progressPerc = progressPerc;
this.progressText = progressText;
}
}
}
</script>

View File

@ -0,0 +1,69 @@
<template>
<div class="t-message-list c-overlay__contents">
<div class="c-overlay__top-bar">
<div class="c-overlay__dialog-title">Notifications</div>
<div class="c-overlay__dialog-hint">
{{ notificationsCountDisplayMessage(notifications.length) }}
</div>
</div>
<div class="w-messages c-overlay__messages">
<notification-message
v-for="notification in notifications"
:key="notification.model.timestamp"
:notification="notification"
/>
</div>
</div>
</template>
<script>
import NotificationMessage from './NotificationMessage.vue';
export default {
components: {
NotificationMessage
},
inject: ['openmct'],
props: {
notifications: {
type: Array,
required: true
}
},
data() {
return {}
},
mounted() {
this.openOverlay();
},
methods: {
openOverlay() {
this.overlay = this.openmct.overlays.overlay({
element: this.$el,
size: 'large',
dismissable: true,
buttons: [
{
label: 'Clear All Notifications',
emphasis: true,
callback:() => {
this.$emit('clear-all');
this.overlay.dismiss();
}
}
],
onDestroy: () => {
this.$emit('close', false);
}
});
},
notificationsCountDisplayMessage(count) {
if (count > 1 || count === 0) {
return `Displaying ${count} notifications`;
} else {
return `Displaying ${count} notification`;
}
}
}
}
</script>

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 * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -19,15 +19,25 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import Vue from 'vue';
import NotificationIndicator from './components/NotificationIndicator.vue';
define( export default function plugin() {
[], return function install(openmct) {
function () { let component = new Vue ({
provide: {
openmct
},
components: {
NotificationIndicator: NotificationIndicator
},
template: '<NotificationIndicator></NotificationIndicator>'
}),
indicator = {
key: 'notifications-indicator',
element: component.$mount().$el
};
function NotificationIndicator() {} openmct.indicators.add(indicator);
};
NotificationIndicator.template = 'notificationIndicatorTemplate'; }
return NotificationIndicator;
}
);

View File

@ -0,0 +1,71 @@
/*****************************************************************************
* 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 NotificationIndicatorPlugin from './plugin.js';
import Vue from 'vue';
import {
createOpenMct
} from 'testUtils';
describe('the plugin', () => {
let notificationIndicatorPlugin,
openmct,
indicatorObject,
indicatorElement,
parentElement,
mockMessages = ['error', 'test', 'notifications'];
beforeEach((done) => {
openmct = createOpenMct();
notificationIndicatorPlugin = new NotificationIndicatorPlugin();
openmct.install(notificationIndicatorPlugin);
parentElement = document.createElement('div');
indicatorObject = openmct.indicators.indicatorObjects.find(indicator => indicator.key === 'notifications-indicator');
indicatorElement = indicatorObject.element;
openmct.on('start', () => {
mockMessages.forEach(message => {
openmct.notifications.error(message);
});
done();
});
openmct.startHeadless();
});
describe('the indicator plugin element', () => {
beforeEach(() => {
parentElement.append(indicatorElement);
return Vue.nextTick();
});
it('notifies the user of the number of notifications', () => {
let notificationCountElement = parentElement.querySelector('.c-indicator__count');
expect(notificationCountElement.innerText).toEqual(mockMessages.length.toString());
});
});
});

View File

@ -152,7 +152,7 @@ function (
MCTChartController.prototype.destroy = function () { MCTChartController.prototype.destroy = function () {
this.isDestroyed = true; this.isDestroyed = true;
this.stopListening(); this.stopListening();
_.invoke(this.lines, 'destroy'); this.lines.forEach(line => line.destroy());
DrawLoader.releaseDrawAPI(this.drawAPI); DrawLoader.releaseDrawAPI(this.drawAPI);
}; };

View File

@ -44,7 +44,7 @@ define([
this.initialize(options); this.initialize(options);
} }
_.extend(Collection.prototype, EventEmitter.prototype); Object.assign(Collection.prototype, EventEmitter.prototype);
eventHelpers.extend(Collection.prototype); eventHelpers.extend(Collection.prototype);
Collection.extend = extend; Collection.extend = extend;
@ -105,12 +105,7 @@ define([
}; };
Collection.prototype.indexOf = function (model) { Collection.prototype.indexOf = function (model) {
return _.findIndex( return this.models.findIndex(m => m === model);
this.models,
function (m) {
return m === model;
}
);
}; };
Collection.prototype.remove = function (model) { Collection.prototype.remove = function (model) {

View File

@ -49,7 +49,7 @@ define([
this.initialize(options); this.initialize(options);
} }
_.extend(Model.prototype, EventEmitter.prototype); Object.assign(Model.prototype, EventEmitter.prototype);
eventHelpers.extend(Model.prototype); eventHelpers.extend(Model.prototype);
Model.extend = extend; Model.extend = extend;

View File

@ -146,7 +146,7 @@ define([
strategy = 'minmax'; strategy = 'minmax';
} }
options = _.extend({}, { size: 1000, strategy, filters: this.filters }, options || {}); options = Object.assign({}, { size: 1000, strategy, filters: this.filters }, options || {});
if (!this.unsubscribe) { if (!this.unsubscribe) {
this.unsubscribe = this.openmct this.unsubscribe = this.openmct
@ -160,6 +160,7 @@ define([
); );
} }
/* eslint-disable you-dont-need-lodash-underscore/concat */
return this.openmct return this.openmct
.telemetry .telemetry
.request(this.domainObject, options) .request(this.domainObject, options)
@ -171,6 +172,7 @@ define([
.value(); .value();
this.reset(newPoints); this.reset(newPoints);
}.bind(this)); }.bind(this));
/* eslint-enable you-dont-need-lodash-underscore/concat */
}, },
/** /**
* Update x formatter on x change. * Update x formatter on x change.
@ -270,7 +272,7 @@ define([
* @private * @private
*/ */
sortedIndex: function (point) { sortedIndex: function (point) {
return _.sortedIndex(this.data, point, this.getXVal); return _.sortedIndexBy(this.data, point, this.getXVal);
}, },
/** /**
* Update min/max stats for the series. * Update min/max stats for the series.
@ -322,7 +324,15 @@ define([
* a point to the end without dupe checking. * a point to the end without dupe checking.
*/ */
add: function (point, appendOnly) { add: function (point, appendOnly) {
var insertIndex = this.data.length; var insertIndex = this.data.length,
currentYVal = this.getYVal(point),
lastYVal = this.getYVal(this.data[insertIndex - 1]);
if (this.isValueInvalid(currentYVal) && this.isValueInvalid(lastYVal)) {
console.warn('[Plot] Invalid Y Values detected');
return;
}
if (!appendOnly) { if (!appendOnly) {
insertIndex = this.sortedIndex(point); insertIndex = this.sortedIndex(point);
if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) { if (this.getXVal(this.data[insertIndex]) === this.getXVal(point)) {
@ -332,11 +342,21 @@ define([
return; return;
} }
} }
this.updateStats(point); this.updateStats(point);
point.mctLimitState = this.evaluate(point); point.mctLimitState = this.evaluate(point);
this.data.splice(insertIndex, 0, point); this.data.splice(insertIndex, 0, point);
this.emit('add', point, insertIndex, this); this.emit('add', point, insertIndex, this);
}, },
/**
*
* @private
*/
isValueInvalid: function (val) {
return Number.isNaN(val) || val === undefined;
},
/** /**
* Remove a point from the data array and notify listeners. * Remove a point from the data array and notify listeners.
* @private * @private

View File

@ -101,11 +101,11 @@ define([
var plotObject = this.plot.get('domainObject'); var plotObject = this.plot.get('domainObject');
if (plotObject.type === 'telemetry.plot.overlay') { if (plotObject.type === 'telemetry.plot.overlay') {
var persistedIndex = _.findIndex(plotObject.configuration.series, function (s) { var persistedIndex = plotObject.configuration.series.findIndex(s => {
return _.isEqual(identifier, s.identifier); return _.isEqual(identifier, s.identifier);
}); });
var configIndex = _.findIndex(this.models, function (m) { var configIndex = this.models.findIndex(m => {
return _.isEqual(m.domainObject.identifier, identifier); return _.isEqual(m.domainObject.identifier, identifier);
}); });

View File

@ -182,21 +182,6 @@ define([
this.set('format', yFormat.format.bind(yFormat)); this.set('format', yFormat.format.bind(yFormat));
this.set('values', yMetadata.values); this.set('values', yMetadata.values);
if (!label) { if (!label) {
var labelUnits = series.map(function (s) {
return s.metadata.value(s.get('yKey')).units;
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
return '';
}, undefined);
if (labelUnits) {
this.set('label', labelUnits);
return;
}
var labelName = series.map(function (s) { var labelName = series.map(function (s) {
return s.metadata.value(s.get('yKey')).name; return s.metadata.value(s.get('yKey')).name;
}).reduce(function (a, b) { }).reduce(function (a, b) {
@ -208,7 +193,28 @@ define([
} }
return ''; return '';
}, undefined); }, undefined);
this.set('label', labelName);
if (labelName) {
this.set('label', labelName);
return;
}
var labelUnits = series.map(function (s) {
return s.metadata.value(s.get('yKey')).units;
}).reduce(function (a, b) {
if (a === undefined) {
return b;
}
if (a === b) {
return a;
}
return '';
}, undefined);
if (labelUnits) {
this.set('label', labelUnits);
return;
}
} }
}, },
defaults: function (options) { defaults: function (options) {

View File

@ -51,7 +51,7 @@ define([
} }
} }
_.extend(Draw2D.prototype, EventEmitter.prototype); Object.assign(Draw2D.prototype, EventEmitter.prototype);
eventHelpers.extend(Draw2D.prototype); eventHelpers.extend(Draw2D.prototype);
// Convert from logical to physical x coordinates // Convert from logical to physical x coordinates

View File

@ -78,7 +78,7 @@ define([
this.listenTo(this.canvas, "webglcontextlost", this.onContextLost, this); this.listenTo(this.canvas, "webglcontextlost", this.onContextLost, this);
} }
_.extend(DrawWebGL.prototype, EventEmitter.prototype); Object.assign(DrawWebGL.prototype, EventEmitter.prototype);
eventHelpers.extend(DrawWebGL.prototype); eventHelpers.extend(DrawWebGL.prototype);
DrawWebGL.prototype.onContextLost = function (event) { DrawWebGL.prototype.onContextLost = function (event) {

View File

@ -23,7 +23,7 @@
define([ define([
'../configuration/configStore', '../configuration/configStore',
'../lib/eventHelpers', '../lib/eventHelpers',
'../../../../api/objects/object-utils', 'objectUtils',
'lodash' 'lodash'
], function ( ], function (
configStore, configStore,

View File

@ -31,7 +31,7 @@ define([
function dynamicPathForKey(key) { function dynamicPathForKey(key) {
return function (object, model) { return function (object, model) {
var modelIdentifier = model.get('identifier'); var modelIdentifier = model.get('identifier');
var index = _.findIndex(object.configuration.series, function (s) { var index = object.configuration.series.findIndex(s => {
return _.isEqual(s.identifier, modelIdentifier); return _.isEqual(s.identifier, modelIdentifier);
}); });
return 'configuration.series[' + index + '].' + key; return 'configuration.series[' + index + '].' + key;

View File

@ -73,10 +73,10 @@ define([
if (range.max === '' || range.max === null || typeof range.max === 'undefined') { if (range.max === '' || range.max === null || typeof range.max === 'undefined') {
return 'Must specify Maximum'; return 'Must specify Maximum';
} }
if (_.isNaN(Number(range.min))) { if (Number.isNaN(Number(range.min))) {
return 'Minimum must be a number.'; return 'Minimum must be a number.';
} }
if (_.isNaN(Number(range.max))) { if (Number.isNaN(Number(range.max))) {
return 'Maximum must be a number.'; return 'Maximum must be a number.';
} }
if (Number(range.min) > Number(range.max)) { if (Number(range.min) > Number(range.max)) {

View File

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

View File

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

View File

@ -76,7 +76,7 @@ define([
if (childObj) { if (childObj) {
var index = telemetryObjects.indexOf(childObj); var index = telemetryObjects.indexOf(childObj);
telemetryObjects.splice(index, 1); telemetryObjects.splice(index, 1);
$scope.$broadcast('plot:tickWidth', _.max(tickWidthMap)); $scope.$broadcast('plot:tickWidth', Math.max(...Object.values(tickWidthMap)));
} }
} }

View File

@ -0,0 +1,72 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import PlotlyViewLayout from './components/PlotlyViewLayout.vue';
import Vue from 'vue';
export default function PlotlyViewProvider(openmct) {
return {
key: 'plotlyPlot',
name: 'Plotly Plot',
cssClass: 'icon-plot-overlay',
canView: function (domainObject) {
return domainObject.type === 'plotlyPlot';
},
canEdit: function (domainObject) {
return domainObject.type === 'plotlyPlot';
},
view: function (domainObject) {
let component;
return {
show: function (element, isEditing) {
component = new Vue({
provide: {
openmct,
domainObject
},
el: element,
components: {
PlotlyViewLayout
},
data() {
return {
isEditing
}
},
template: '<plotly-view-layout :isEditing="isEditing"></plotly-view-layout>'
});
},
onEditModeChange: function (isEditing) {
component.isEditing = isEditing;
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}

View File

@ -0,0 +1,237 @@
<template>
<div class="l-view-section"></div>
</template>
<script>
import Plotly from 'plotly.js/dist/plotly.min.js';
import moment from 'moment'
export default {
inject: ['openmct', 'domainObject'],
data: function () {
return {
telemetryObjects: [],
bounds: {},
timeRange: 0,
plotData: {},
subscriptions: {}
}
},
mounted() {
this.plotElement = document.querySelector('.l-view-section');
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addTelemetry);
this.composition.on('remove', this.removeTelemetry);
this.composition.load();
this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('clock', this.changeClock);
},
destroyed() {
Object.keys(this.subscriptions)
.forEach(subscription => this.unsubscribe(subscription));
},
methods: {
changeClock() {
if (this.openmct.time.clock()) {
//Plotly.purge(this.plotElement);
this.telemetryObjects.forEach((telemetryObject, index) => {
this.subscribeTo(telemetryObject, index);
});
}
},
addTelemetry(telemetryObject) {
this.telemetryObjects.push(telemetryObject);
const index = this.telemetryObjects.length - 1;
this.requestHistory(telemetryObject, index, true);
this.subscribeTo(telemetryObject, index);
},
subscribeTo(telemetryObject, index) {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
this.updateData(datum, index);
});
},
unsubscribe(keyString) {
this.subscriptions[keyString]();
delete this.subscriptions[keyString];
},
refreshData(bounds, isTick) {
this.bounds = bounds;
this.telemetryObjects.forEach((telemetryObject, index) => {
if(!isTick) {
this.requestHistory(telemetryObject, index, false);
} else {
if (this.timeRange === 0 || this.timeRange !== this.bounds.end - this.bounds.start) {
this.timeRange = this.bounds.end - this.bounds.start;
this.requestHistory(telemetryObject, index, false);
}
}
});
},
requestHistory(telemetryObject, index, isAdd) {
this.openmct
.telemetry
.request(telemetryObject, {
start: this.bounds.start,
end: this.bounds.end
})
.then((telemetryData) => {
this.addTrace(telemetryData, telemetryObject, index, isAdd);
});
},
getLayout(telemetryObject, isFixed) {
return {
hovermode: 'compare',
hoverdistance: -1,
autosize: "true",
showlegend: true,
legend: {
y: 1.1,
"orientation": "h"
},
font: {
family: "'Helvetica Neue', Helvetica, Arial, sans-serif",
size: "12px",
color: "#666"
},
xaxis: { // hardcoded as UTC for now
title: 'UTC',
zeroline: false,
range: isFixed ? 'undefined' : [
this.formatDatumX({utc: this.bounds.start}),
this.formatDatumX({utc: this.bounds.start})
]
},
yaxis: {
title: this.getYAxisLabel(telemetryObject),
zeroline: false
},
margin: {
l: 40,
r: 10,
b: 40,
t: 10
},
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent'
}
},
removeTelemetry(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.unsubscribe(keyString);
this.telemetryObjects = this.telemetryObjects.filter(object => !(identifier.key === object.identifier.key));
if (!this.telemetryObjects.length) {
//Plotly.purge(this.plotElement);
} else {
//Plotly.deleteTraces(this.plotElement, this.telemetryObjects.length - 1);
}
},
getYAxisLabel(telemetryObject) {
this.setYAxisProp(telemetryObject);
const valueMetadatas = this.openmct.telemetry.getMetadata(telemetryObject).values();
const index = valueMetadatas.findIndex(value => value.key === this.yAxisProp);
const yLabel = valueMetadatas[index].name;
return yLabel;
},
setYAxisProp(telemetryObject) {
if (telemetryObject.type === 'generator') {
this.yAxisProp = 'sin';
} else if (telemetryObject.type === 'example.state-generator') {
this.yAxisProp = 'state';
} else if (telemetryObject.type === 'conditionSet') {
this.yAxisProp = 'output';
}
},
formatDatumX(datum) {
let timestamp = moment.utc(datum.utc).format('YYYY-MM-DDTHH:mm:ss[Z]');
return timestamp;
},
formatDatumY(datum) {
return datum.sin;
},
addTrace(telemetryData, telemetryObject, index, isAdd) {
let x = [];
let y = [];
// temp palette for demo
const colors = ['rgb(32, 178, 170)', 'rgb(154, 205, 50)', 'rgb(255, 140, 0)'];
telemetryData.forEach((datum) => {
x.push(this.formatDatumX(datum));
y.push(this.formatDatumY(datum));
})
let traceData = [{ // trace configuration
x,
y,
name: telemetryObject.name,
type: 'scattergl',
mode: 'lines+markers',
marker: {
size: 5
},
line: {
color: colors[index], // to set new color for each trace
shape: 'linear',
width: 1.5
}
}];
this.plotData[telemetryObject.identifier.key] = traceData[0];
if (!this.plotElement.childNodes.length) { // not traces yet, so create new plot
// Plotly.newPlot(
// this.plotElement,
// traceData,
// this.getLayout(telemetryObject, true),
// {
// displayModeBar: false, // turns off hover-activated toolbar
// staticPlot: true // turns off hover effects on datapoints
// }
// );
} else {
// if (isAdd) { // add a new trace to existing plot
// Plotly.addTraces(this.plotElement, traceData);
// } else { // update existing trace with new data (bounds change)
// Plotly.react(this.plotElement, Object.values(this.plotData), this.getLayout(telemetryObject, false));
// this.updatePlotRange();
// }
}
},
updateData(datum, index) {
// plot all datapoints within bounds
if (datum.utc <= this.bounds.end) {
// Plotly.extendTraces(
// this.plotElement,
// {
// x: [[this.formatDatumX(datum)]],
// y: [[this.formatDatumY(datum)]]
// },
// [index]
// );
this.updatePlotRange();
}
},
updatePlotRange() {
let newRange = {
'xaxis.range': [
this.formatDatumX({utc: this.bounds.start}),
this.formatDatumX({utc: this.bounds.end})
]
};
// Plotly.relayout(this.plotElement, newRange);
}
}
}
</script>

View File

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

View File

@ -34,6 +34,7 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin', './URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin', './telemetryMean/plugin',
'./plot/plugin', './plot/plugin',
'./plotlyPlot/plugin',
'./telemetryTable/plugin', './telemetryTable/plugin',
'./staticRootPlugin/plugin', './staticRootPlugin/plugin',
'./notebook/plugin', './notebook/plugin',
@ -51,7 +52,8 @@ define([
'./conditionWidget/plugin', './conditionWidget/plugin',
'./themes/espresso', './themes/espresso',
'./themes/maelstrom', './themes/maelstrom',
'./themes/snow' './themes/snow',
'./notificationIndicator/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
@ -66,6 +68,7 @@ define([
URLIndicatorPlugin, URLIndicatorPlugin,
TelemetryMean, TelemetryMean,
PlotPlugin, PlotPlugin,
PlotlyPlotPlugin,
TelemetryTablePlugin, TelemetryTablePlugin,
StaticRootPlugin, StaticRootPlugin,
Notebook, Notebook,
@ -83,7 +86,8 @@ define([
ConditionWidgetPlugin, ConditionWidgetPlugin,
Espresso, Espresso,
Maelstrom, Maelstrom,
Snow Snow,
NotificationIndicator
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
@ -171,8 +175,8 @@ define([
plugins.ExampleImagery = ExampleImagery; plugins.ExampleImagery = ExampleImagery;
plugins.ImageryPlugin = ImageryPlugin; plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin; plugins.Plot = PlotPlugin;
plugins.PlotlyPlot = PlotlyPlotPlugin.default;
plugins.TelemetryTable = TelemetryTablePlugin; plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget; plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean; plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin; plugins.URLIndicator = URLIndicatorPlugin;
@ -192,6 +196,7 @@ define([
plugins.Snow = Snow.default; plugins.Snow = Snow.default;
plugins.Condition = ConditionPlugin.default; plugins.Condition = ConditionPlugin.default;
plugins.ConditionWidget = ConditionWidgetPlugin.default; plugins.ConditionWidget = ConditionWidgetPlugin.default;
plugins.NotificationIndicator = NotificationIndicator.default;
return plugins; return plugins;
}); });

View File

@ -1,5 +1,5 @@
define([ define([
'../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
objectUtils objectUtils
) { ) {

View File

@ -1,6 +1,6 @@
define ([ define ([
'./ConditionEvaluator', './ConditionEvaluator',
'../../../api/objects/object-utils', 'objectUtils',
'EventEmitter', 'EventEmitter',
'zepto', 'zepto',
'lodash' 'lodash'
@ -9,7 +9,8 @@ define ([
objectUtils, objectUtils,
EventEmitter, EventEmitter,
$, $,
_ _,
) { ) {
/** /**

View File

@ -5,7 +5,7 @@ define([
'./TestDataManager', './TestDataManager',
'./WidgetDnD', './WidgetDnD',
'./eventHelpers', './eventHelpers',
'../../../api/objects/object-utils', 'objectUtils',
'lodash', 'lodash',
'zepto' 'zepto'
], function ( ], function (

View File

@ -1,6 +1,6 @@
define([ define([
'./Select', './Select',
'../../../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
Select, Select,
objectUtils objectUtils

View File

@ -22,7 +22,7 @@
define([ define([
'./SummaryWidgetEvaluator', './SummaryWidgetEvaluator',
'../../../../api/objects/object-utils' 'objectUtils'
], function ( ], function (
SummaryWidgetEvaluator, SummaryWidgetEvaluator,
objectUtils objectUtils

View File

@ -23,7 +23,7 @@
define([ define([
'./SummaryWidgetRule', './SummaryWidgetRule',
'../eventHelpers', '../eventHelpers',
'../../../../api/objects/object-utils', 'objectUtils',
'lodash' 'lodash'
], function ( ], function (
SummaryWidgetRule, SummaryWidgetRule,
@ -80,10 +80,12 @@ define([
} }
}.bind(this); }.bind(this);
/* eslint-disable you-dont-need-lodash-underscore/map */
unsubscribes = _.map( unsubscribes = _.map(
realtimeStates, realtimeStates,
this.subscribeToObjectState.bind(this, updateCallback) this.subscribeToObjectState.bind(this, updateCallback)
); );
/* eslint-enable you-dont-need-lodash-underscore/map */
}.bind(this)); }.bind(this));
return function () { return function () {
@ -151,11 +153,13 @@ define([
SummaryWidgetEvaluator.prototype.getBaseStateClone = function () { SummaryWidgetEvaluator.prototype.getBaseStateClone = function () {
return this.load() return this.load()
.then(function () { .then(function () {
/* eslint-disable you-dont-need-lodash-underscore/values */
return _(this.baseState) return _(this.baseState)
.values() .values()
.map(_.clone) .map(_.clone)
.indexBy('id') .keyBy('id')
.value(); .value();
/* eslint-enable you-dont-need-lodash-underscore/values */
}.bind(this)); }.bind(this));
}; };
@ -182,7 +186,7 @@ define([
* @private. * @private.
*/ */
SummaryWidgetEvaluator.prototype.updateObjectStateFromLAD = function (options, objectState) { SummaryWidgetEvaluator.prototype.updateObjectStateFromLAD = function (options, objectState) {
options = _.extend({}, options, { options = Object.assign({}, options, {
strategy: 'latest', strategy: 'latest',
size: 1 size: 1
}); });
@ -255,10 +259,12 @@ define([
} }
} }
/* eslint-disable you-dont-need-lodash-underscore/map */
var latestTimestamp = _(state) var latestTimestamp = _(state)
.map('timestamps') .map('timestamps')
.sortBy(timestampKey) .sortBy(timestampKey)
.last(); .last();
/* eslint-enable you-dont-need-lodash-underscore/map */
if (!latestTimestamp) { if (!latestTimestamp) {
latestTimestamp = {}; latestTimestamp = {};

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