Compare commits

..

51 Commits

Author SHA1 Message Date
John Hill
b02d063b8b Merge branch 'master' into playwright-karma 2022-03-23 09:02:32 -07:00
dependabot[bot]
93a796353d Bump printj from 1.2.1 to 1.3.1 (#4981)
Bumps [printj](https://github.com/SheetJS/printj) from 1.2.1 to 1.3.1.
- [Release notes](https://github.com/SheetJS/printj/releases)
- [Commits](https://github.com/SheetJS/printj/commits)

---
updated-dependencies:
- dependency-name: printj
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-23 08:03:37 -07:00
dependabot[bot]
18ff16052a Bump jasmine-core from 4.0.0 to 4.0.1 (#4980)
Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-22 21:00:28 -07:00
dependabot[bot]
f1c7fa337d Bump raw-loader from 0.5.1 to 4.0.2 (#4978)
Bumps [raw-loader](https://github.com/webpack-contrib/raw-loader) from 0.5.1 to 4.0.2.
- [Release notes](https://github.com/webpack-contrib/raw-loader/releases)
- [Changelog](https://github.com/webpack-contrib/raw-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/raw-loader/commits/v4.0.2)

---
updated-dependencies:
- dependency-name: raw-loader
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-03-23 03:29:04 +00:00
John Hill
557f29f9bb bump git-rev-sync and add tests (#4959)
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-03-22 20:20:39 -07:00
dependabot[bot]
255ebc9a37 Bump core-js from 3.20.3 to 3.21.1 (#4970)
Bumps [core-js](https://github.com/zloirock/core-js) from 3.20.3 to 3.21.1.
- [Release notes](https://github.com/zloirock/core-js/releases)
- [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zloirock/core-js/compare/v3.20.3...v3.21.1)

---
updated-dependencies:
- dependency-name: core-js
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-22 18:03:09 -07:00
Joe Pea
ca7fbe58e3 code coverage with babel istanbul (#4649)
* WIP add instanbul code coverage to vue files

* move webpack coverage config to a separate file for test:coverage, pin dependencies, remove istanbul-instrumenter-loader

* dont include node_modules in babel config, it breaks istanbul for some reason

* ignore spec files from coverage

* document coverage files, remove unused karma config

* use test instead of test:coverage in config.yml, disable code and link to issue to enable coverage in Vue <template>s
2022-03-22 16:04:23 -07:00
dependabot[bot]
b347f35892 Bump file-loader from 6.1.0 to 6.2.0 (#4834)
Bumps [file-loader](https://github.com/webpack-contrib/file-loader) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/webpack-contrib/file-loader/releases)
- [Changelog](https://github.com/webpack-contrib/file-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/file-loader/compare/v6.1.0...v6.2.0)

---
updated-dependencies:
- dependency-name: file-loader
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2022-03-22 13:33:02 -07:00
Scott Bell
28d5d72834 Event Generator now shows data in fixed time mode (#4883)
* fixed issue and added tests
* exclude leaky test
* ignore case when looking for testing instructions
2022-03-21 15:37:49 -07:00
Jamie V
0df33730f7 on route change, calling showTab (#4955) 2022-03-21 16:29:47 -05:00
John Hill
7b2ff8fa15 [Docs] add browserlist and linting capability (#4811)
* add browserlist to package.json

* add browserlist linting

* add eslint compat package

* Add blurb about browser support

* add ios safari

* remove node

* add comment and simple disable

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: Scott Bell <scott@traclabs.com>
2022-03-21 20:27:18 +01:00
Joe Pea
d80b692354 Update eslint (#4554)
Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 7.20.0 to 8.0.3.
- [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases)
- [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v7.20.0...v8.0.3)

---
updated-dependencies:
- dependency-name: eslint-plugin-vue
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
* bump eslint to 8.11.0
* bump eslint-plugin-vue to 8.5.0
* disable eslint rule for multi-word component names. TODO enable it and follow conventions

Co-authored-by: Nikhil Mandlik <nikhil.k.mandlik@nasa.gov>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-03-21 11:40:35 -07:00
Scott Bell
4205abdc80 Add Example Generator end to end test (#4949)
* add test

* added name to test and changed comment
2022-03-18 06:57:50 -07:00
Jamie V
492ff2fa64 [e2e] Persistence tests (#4943)
* initial setup for createButton e2e test

* insignificant change

* export as json

* import tests

* added first of two tests

* add stub

* fixme

* move objects

* added test for menu options for non persistable items

* removing "only" for debuggin test

* removing debug code

* soft assertion for multiple expects

Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-03-17 14:30:45 -07:00
John Hill
93a81a1369 [CI] Update GHA to include a specific playwright dependency version (#4944)
* Update e2e-pr.yml

* Update e2e-pr.yml

* Update e2e-pr.yml

* visual

Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-03-16 22:05:50 +00:00
Nikhil
482d8f392c Domain object properties validation not working always (#4893)
* Domain object properties validation not working always #4849

* stub of a test

* set local execution to chrome

* skip visual test

* use input event for changes and throttle update.

* moved file to correct location

* fixed e2e tests.

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-03-16 21:53:58 +00:00
John Hill
67234c70a4 [Build] 🐰🐰🐰 Remove the last carrots 🐰🐰🐰 (#4941)
* [Build] 🐰🐰🐰 Remove the last carrots 🐰🐰🐰

* Update package.json

Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
2022-03-16 21:44:41 +00:00
Nikhil
e9680e975f Form validation error messages are not being displayed properly (#4887)
* Form validation error messages are not being displayed properly #4697

* e2e Tests

* fixed element.checkValidity test

* remove comment

* adding timeconductor tests

* rename

Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-03-16 13:54:46 -07:00
Jamie V
e691a89682 Correctly use creatable attribute and persistability when working with domainObjects (#4898)
* making move action location check persistability

* adding persistence check instead of creatability for styles

* added check for link action to make sure parent is persistable

* debug

* adding parent to link action and move action form location controls so they can be used in the form

* adding parent persistability check for duplicate

* updating multilple actions appliesTo methods to check for persistability

* updated the tree to not require an initial selection if being used in a form

* remove noneditable folder plugin

* added persistence check for the parent, in the create wizard

* minor name change

* removing noneditabl folder from default plugins as well

* checking the correct parent for persistability in create wizard

* importing file-saver correctly

* updated tests for import as json

* changes addressing PR review: using consts, removing comments, removing unneccessary code

Co-authored-by: Scott Bell <scott@traclabs.com>
2022-03-14 21:20:06 +01:00
Shefali Joshi
bcd668594d Add source maps to map plan properties to expected properties (#4933) 2022-03-11 10:40:05 -08:00
dependabot[bot]
5471e13d9e Bump @braintree/sanitize-url from 5.0.2 to 6.0.0 (#4928)
Bumps [@braintree/sanitize-url](https://github.com/braintree/sanitize-url) from 5.0.2 to 6.0.0.
- [Release notes](https://github.com/braintree/sanitize-url/releases)
- [Changelog](https://github.com/braintree/sanitize-url/blob/main/CHANGELOG.md)
- [Commits](https://github.com/braintree/sanitize-url/compare/v5.0.2...v6.0.0)

---
updated-dependencies:
- dependency-name: "@braintree/sanitize-url"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 19:53:39 -08:00
John Hill
dae446808e Update with CRUD Operation (#4930)
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-03-09 06:45:10 -08:00
dependabot[bot]
ee9e47f487 Bump @playwright/test from 1.19.1 to 1.19.2 (#4905)
Bumps [@playwright/test](https://github.com/Microsoft/playwright) from 1.19.1 to 1.19.2.
- [Release notes](https://github.com/Microsoft/playwright/releases)
- [Commits](https://github.com/Microsoft/playwright/compare/v1.19.1...v1.19.2)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-03-08 18:49:48 -08:00
dependabot[bot]
003b1ffede Bump actions/setup-node from 2 to 3 (#4900)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-07 18:33:18 -08:00
John Hill
16cb5f3911 [CI] Remove warning output for eslint on platform workflow (#4906) 2022-03-03 14:07:54 -08:00
dependabot[bot]
74b83903c9 Bump actions/checkout from 2 to 3 (#4904)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-02 09:59:13 -08:00
Khalid Adil
6a470fba1a Static root plugin not loading after namespace/key changes in OpenMCT #4684
Static root plugin not loading after namespace/key changes in OpenMCT
2022-02-28 11:51:34 -08:00
Jamie V
4e7debabb1 [Notebook] Add active user to entries (#4764)
* if user provider, user added to notebook enntries and snapshot entries, updated code to work with asynnc nature of user api

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-02-22 12:37:47 -08:00
Michael Rogers
384e36920c 3175 - Enable listening to clearData action for Imagery (#4733)
* Add clearData listener for imageryData module

* Remove commented out code

* Updated imagery clear data test

* Adjusted telemetry stub to return empty array if data cleared

* Remove forced test

* Restub telemetry before

* Cleanup and reset clear data boolean after

* Remove double blank line

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2022-02-18 08:48:48 -06:00
dependabot[bot]
d4429f9686 Bump cross-env from 6.0.3 to 7.0.3 (#4837)
Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 6.0.3 to 7.0.3.
- [Release notes](https://github.com/kentcdodds/cross-env/releases)
- [Changelog](https://github.com/kentcdodds/cross-env/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kentcdodds/cross-env/compare/v6.0.3...v7.0.3)

---
updated-dependencies:
- dependency-name: cross-env
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-17 10:34:14 -08:00
John Hill
0b2d08078b [CI] Add windows OS and Mac OS to CI testing (#4840)
* Update e2e tests to run on windows and linux

* Add pr-platform to run against all supported archictures

* Update dependabot to run pr:platform

* Update to run in fail-fast false

* remove x86

* Update pr-platform.yml

Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-02-17 14:52:46 +00:00
John Hill
3bbc9e1582 [CI] Enable Per PR Lighthouse CI Execution, Run nightly (#4817)
* [CI] Add lighthouse to our deps to track with dependabot

lighthouse ci is still actively maintained and will be following a traditional release model. We should continue to use this package until it's functionality is replaced in playwright

* Add lighthouse to dependencies to track with dependabot

* Allow lighthouse to be triggered from PRs

* Update lighthouse.yml

* add lhci scrript

* bump to 16

* remove from deps until node 18

* document steps and add caching. Revert to 14

* ignore exit codes

* add secret for app

* remove env for baseline

Co-authored-by: unlikelyzero <jchill2@gmail.com>
Co-authored-by: Nikhil <nikhil.k.mandlik@nasa.gov>
2022-02-16 17:12:00 -08:00
John Hill
af0420361b [CI] Update playwright to 1.19.1 (#4873)
* [CI] Update playwright to 1.19.1

* Update config.yml
2022-02-16 15:12:09 -08:00
dependabot[bot]
896f0ca3f4 Bump moment from 2.25.3 to 2.29.1 (#4869)
Bumps [moment](https://github.com/moment/moment) from 2.25.3 to 2.29.1.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.25.3...2.29.1)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-16 11:22:51 -08:00
dependabot[bot]
28c5405a01 Bump actions/github-script from 5 to 6 (#4853)
Bumps [actions/github-script](https://github.com/actions/github-script) from 5 to 6.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-15 15:31:06 -08:00
John Hill
d114353556 [CI] Update PR Cop to run on more events (#4855)
* [CI] Update PR Cop to run on more events

* Update prcop.yml

* Update prcop.yml

* Update e2e PR

* Update e2e-visual.yml
2022-02-15 14:13:52 -08:00
John Hill
f40398807e [CI] Update CI to use node16 by default (#4868)
* Update node versions to 16

* New Line

Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-02-15 14:08:53 -08:00
John Hill
81f440e1e6 [Docs] Update Github Bug Report Template with envinfo (#4858)
* [Docs] Update Github Bug Report Template with envinfo

* Moving Impact Checklist to bottom so that folks can file and click without markdown x

* Add to npm run scripts
2022-02-15 14:00:37 -08:00
John Hill
c6d6400131 [CI] Bump playwright to 1.19.0 (#4856)
* bump playwright

* remove playwright dependency and leave playwrite/test

Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-02-15 10:57:24 -06:00
dependabot[bot]
55828af1ec Bump file-saver from 1.3.8 to 2.0.5 (#4830)
* Bump file-saver from 1.3.8 to 2.0.5

Bumps [file-saver](https://github.com/eligrey/FileSaver.js) from 1.3.8 to 2.0.5.
- [Release notes](https://github.com/eligrey/FileSaver.js/releases)
- [Changelog](https://github.com/eligrey/FileSaver.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eligrey/FileSaver.js/commits)

---
updated-dependencies:
- dependency-name: file-saver
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix paths

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Scott Bell <scott@traclabs.com>
2022-02-15 14:07:58 +01:00
Andrew Henry
34b951f4c6 Bumped dependency versions to fix build issues in Node 16 and later (#4866)
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2022-02-14 17:15:21 -08:00
Shefali Joshi
a7d4006fee Sprint 2.0.1 (#4861)
* Prep for release 1.8.6

* Update version to 2.0.1
2022-02-14 13:00:32 -08:00
Shefali Joshi
a71485f820 Prep for release 1.8.6 (#4859) 2022-02-14 10:13:08 -08:00
unlikelyzero
079956d7ae Merge branch 'master' of https://github.com/nasa/openmct into playwright-karma 2022-01-11 08:30:33 -08:00
unlikelyzero
c81c5f7c25 Add Safari Support 2022-01-11 08:29:04 -08:00
John Hill
986083f3c7 Updated config.yml 2022-01-10 19:12:44 -08:00
John Hill
cb7c9fd4f9 Updated config.yml 2022-01-10 18:52:06 -08:00
unlikelyzero
8b8f5c4df4 Move to playwright browsers 2022-01-10 18:41:02 -08:00
unlikelyzero
80fc393b54 Merge branch 'master' of https://github.com/nasa/openmct 2022-01-10 16:12:41 -08:00
unlikelyzero
58138e8554 Merge branch 'master' of https://github.com/nasa/openmct 2021-12-29 17:08:50 -08:00
unlikelyzero
54bed23267 No need for allure reporting in visual tests 2021-12-23 11:11:51 -08:00
156 changed files with 3466 additions and 1959 deletions

View File

@@ -2,7 +2,7 @@ version: 2.1
executors:
pw-focal-development:
docker:
- image: mcr.microsoft.com/playwright:v1.18.1-focal
- image: mcr.microsoft.com/playwright:v1.19.1-focal
environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
parameters:
@@ -21,7 +21,7 @@ commands:
- restore_cache_cmd:
node-version: << parameters.node-version >>
- node/install:
install-npm: true
install-npm: false
node-version: << parameters.node-version >>
- run: npm install
restore_cache_cmd:
@@ -47,6 +47,7 @@ commands:
paths:
- ~/.npm
- node_modules
- ~/.cache/ms-playwright
generate_and_store_version_and_filesystem_artifacts:
description: "Track important packages and files"
steps:
@@ -64,7 +65,6 @@ commands:
- run: curl -Os https://uploader.codecov.io/latest/linux/codecov;chmod +x codecov;./codecov
orbs:
node: circleci/node@4.9.0
browser-tools: circleci/browser-tools@1.2.3
jobs:
npm-audit:
parameters:
@@ -76,7 +76,7 @@ jobs:
node-version: <<parameters.node-version>>
- run: npm audit --audit-level=low
- generate_and_store_version_and_filesystem_artifacts
node14-lint:
lint:
parameters:
node-version:
type: string
@@ -96,26 +96,9 @@ jobs:
steps:
- build_and_install:
node-version: <<parameters.node-version>>
- when:
condition:
equal: [ "FirefoxESR", <<parameters.browser>> ]
steps:
- browser-tools/install-firefox:
version: "91.4.0esr" #https://archive.mozilla.org/pub/firefox/releases/
- when:
condition:
equal: [ "FirefoxHeadless", <<parameters.browser>> ]
steps:
- browser-tools/install-firefox
- when:
condition:
equal: [ "ChromeHeadless", <<parameters.browser>> ]
steps:
- browser-tools/install-chrome:
replace-existing: false
- run: npm run test:coverage -- --browsers=<<parameters.browser>>
- save_cache_cmd:
node-version: <<parameters.node-version>>
- run: npm run test:coverage -- --browsers=<<parameters.browser>>
- store_test_results:
path: dist/reports/tests/
- store_artifacts:
@@ -141,45 +124,53 @@ jobs:
workflows:
overall-circleci-commit-status: #These jobs run on every commit
jobs:
- node14-lint:
node-version: lts/fermium
- lint:
node-version: lts/gallium
- unit-test:
name: node12-chrome
node-version: lts/erbium
browser: ChromeHeadless
browser: PlaywrightChrome
- unit-test:
name: node14-chrome
node-version: lts/fermium
browser: ChromeHeadless
browser: PlaywrightChrome
post-steps:
- upload_code_covio
- upload_code_covio
- unit-test:
name: node16-chrome
node-version: lts/gallium
browser: ChromeHeadless
- e2e-test:
name: e2e-ci
node-version: lts/fermium
node-version: lts/gallium
suite: ci
the-nightly: #These jobs do not run on PRs, but against master at night
the-nightly: #These jobs do not run on PRs, but against master each night
jobs:
- unit-test:
name: node12-firefoxESR-nightly
node-version: lts/erbium
browser: FirefoxESR
- unit-test:
name: node12-chrome-nightly
node-version: lts/erbium
browser: ChromeHeadless
browser: PlaywrightChrome
- unit-test:
name: node14-firefox-nightly
node-version: lts/fermium
browser: FirefoxHeadless
browser: PlaywrightFirefox
- unit-test:
name: node14-chrome-nightly
name: node14-chrome-canary-nightly
node-version: lts/fermium
browser: PlaywrightChromeCanary
- unit-test:
name: node14-safari-nightly
node-version: lts/fermium
browser: PlaywrightSafari
- unit-test:
name: node16-chrome-nightly
node-version: lts/gallium
browser: ChromeHeadless
- npm-audit:
node-version: lts/fermium
node-version: lts/gallium
- e2e-test:
name: e2e-full-nightly
node-version: lts/fermium
node-version: lts/gallium
suite: full
triggers:
- schedule:

View File

@@ -11,12 +11,14 @@ module.exports = {
},
"extends": [
"eslint:recommended",
"plugin:compat/recommended",
"plugin:vue/recommended",
"plugin:you-dont-need-lodash-underscore/compatible"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"requireConfigFile": false,
"allowImportExportEverywhere": true,
"ecmaVersion": 2015,
"ecmaFeatures": {
@@ -35,7 +37,6 @@ module.exports = {
"no-inner-declarations": "off",
"no-use-before-define": ["error", "nofunc"],
"no-caller": "error",
"no-sequences": "error",
"no-irregular-whitespace": "error",
"no-new": "error",
"no-shadow": "error",
@@ -239,13 +240,12 @@ module.exports = {
],
"vue/max-attributes-per-line": ["error", {
"singleline": 1,
"multiline": {
"max": 1,
"allowFirstLine": true
}
"multiline": 1,
}],
"vue/first-attribute-linebreak": "error",
"vue/multiline-html-element-content-newline": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/multi-word-component-names": "off", // TODO enable, align with conventions
"vue/no-mutating-props": "off"
},

View File

@@ -17,15 +17,6 @@ assignees: ''
#### Expected vs Current Behavior
<!--- Tell us what should have happened -->
#### Impact Check List
<!--- Please select from the following options -->
- [ ] Data loss or misrepresented data?
- [ ] Regression? Did this used to work or has it always been broken?
- [ ] Is there a workaround available?
- [ ] Does this impact a critical component?
- [ ] Is this just a visual bug with no functional impact?
#### Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
@@ -35,10 +26,20 @@ assignees: ''
4.
#### Environment
<!--- If encountered on local machine, execute the following:
<!--- npx envinfo --system --browsers --npmPackages --binaries --languages --markdown -->
* Open MCT Version: <!--- date of build, version, or SHA -->
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
* OS:
* Browser:
#### Impact Check List
<!--- Please select from the following options -->
- [ ] Data loss or misrepresented data?
- [ ] Regression? Did this used to work or has it always been broken?
- [ ] Is there a workaround available?
- [ ] Does this impact a critical component?
- [ ] Is this just a visual bug with no functional impact?
#### Additional Information
<!--- Include any screenshots, gifs, or logs which will expedite triage -->

View File

@@ -11,6 +11,8 @@ updates:
- "dependencies"
- "pr:e2e"
- "pr:daveit"
- "pr:visual"
- "pr:platform"
- package-ecosystem: "github-actions"
directory: "/"

View File

@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -2,15 +2,22 @@ name: "e2e-pr"
on:
workflow_dispatch:
pull_request:
types: [ labeled ]
types:
- labeled
- opened
jobs:
e2e-full:
if: ${{ github.event.label.name == 'pr:e2e' }}
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
- name: Trigger Success
uses: actions/github-script@v5
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
@@ -19,11 +26,11 @@ jobs:
repo: "openmct",
body: 'Started e2e Run. Follow along: https://github.com/nasa/openmct/actions/runs/' + context.runId
})
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: npx playwright install-deps
node-version: '16'
- run: npx playwright@1.19.2 install
- run: npm install
- run: npm run test:e2e:full
- name: Archive test results
@@ -32,7 +39,7 @@ jobs:
path: test-results
- name: Test success
if: ${{ success() }}
uses: actions/github-script@v5
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({
@@ -43,7 +50,7 @@ jobs:
})
- name: Test failure
if: ${{ failure() }}
uses: actions/github-script@v5
uses: actions/github-script@v6
with:
script: |
github.rest.issues.createComment({

View File

@@ -4,6 +4,7 @@ on:
pull_request:
types:
- labeled
- opened
schedule:
- cron: '28 21 * * 1-5'
@@ -12,11 +13,11 @@ jobs:
if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '14'
- run: npx playwright install-deps
node-version: '16'
- run: npx playwright@1.19.2 install
- run: npm install
- name: Run the e2e visual tests
run: npm run test:e2e:visual

View File

@@ -10,12 +10,12 @@ jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.version }}
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: '14'
node-version: '16'
- run: npm install
- name: Run the e2e tests
run: npm run test:e2e:ci

View File

@@ -5,16 +5,96 @@ on:
version:
description: 'Which branch do you want to test?' # Limited to branch for now
required: false
default: 'master'
default: 'master'
pull_request:
types:
- labeled
schedule:
- cron: '28 21 * * 1-5'
jobs:
lighthouse:
lighthouse-pr:
if: ${{ github.event.label.name == 'pr:lighthouse' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Checkout Master for Baseline
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.version }}
- uses: actions/setup-node@v2
ref: master #explicitly checkout master for baseline
- name: Install Node 14
uses: actions/setup-node@v3
with:
node-version: '14'
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
- run: lhci autorun
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci against master to generate baseline and ignore exit codes
run: lhci autorun || true
- name: Perform clean checkout of PR
uses: actions/checkout@v3
with:
clean: true
- name: Install Node version which is compatible with PR
uses: actions/setup-node@v3
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci with PR
run: lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
lighthouse-nightly:
if: ${{ github.event.schedule }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Node 14
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci against master to generate baseline
run: lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
lighthouse-dispatch:
if: ${{ github.event.workflow_dispatch }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.version }}
- name: Install Node 14
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
- name: npm install with lighthouse cli
run: npm install && npm install -g @lhci/cli
- name: Run lhci against master to generate baseline
run: lhci autorun

View File

@@ -11,10 +11,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
- run: npm install
- run: npm test
@@ -22,10 +22,10 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm install
- run: npm publish --access public --tag unstable

34
.github/workflows/pr-platform.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: "pr-platform"
on:
workflow_dispatch:
pull_request:
types: [ labeled ]
jobs:
e2e-full:
if: ${{ github.event.label.name == 'pr:platform' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
node_version:
- 12
- 14
- 16
architecture:
- x64
name: Node ${{ matrix.node_version }} - ${{ matrix.architecture }} on ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
architecture: ${{ matrix.architecture }}
- run: npm install
- run: npm test
- run: npm run lint -- --quiet

View File

@@ -1,12 +1,15 @@
name: PRcop
name: PRCop
on:
pull_request:
types:
- opened
- reopened
- edited
- synchronize
- ready_for_review
- review_requested
- review_request_removed
pull_request_review_comment:
types:
- created
@@ -14,7 +17,7 @@ on:
jobs:
prcop:
runs-on: ubuntu-latest
name: PRcop
name: Template Check
steps:
- name: Linting Pull Request
uses: makaroni4/prcop@v1.0.35

5
.npmrc
View File

@@ -1,9 +1,4 @@
loglevel=warn
# Temporary: istanbul-instrumenter-loader is working with webpack 5, but states
# webpack 4 being the latest version it supports, so this legacy-peer-deps
# allows us to install it anyway.
legacy-peer-deps=true
#Prevent folks from ignoring an important error when building from source
engine-strict=true

View File

@@ -65,6 +65,12 @@ Open MCT is built using [`npm`](http://npmjs.com/) and [`webpack`](https://webpa
See our documentation for a guide on [building Applications with Open MCT](https://github.com/nasa/openmct/blob/master/API.md#starting-an-open-mct-application).
## Compatibility
This is a fast moving project and we do our best to test and support the widest possible range of browsers, operating systems, and nodejs APIs. We have a published list of support available in our package.json's `browserslist` key.
If you encounter an issue with a particular browser, OS, or nodejs API, please file a [GitHub issue](https://github.com/nasa/openmct/issues/new/choose)
## Plugins
Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group

9
babel.coverage.js Normal file
View File

@@ -0,0 +1,9 @@
// This is a Babel config that webpack.coverage.js uses in order to instrument
// code with coverage instrumentation.
const babelConfig = {
plugins: [['babel-plugin-istanbul', {
extension: ['.js', '.vue']
}]]
};
module.exports = babelConfig;

View File

@@ -26,7 +26,6 @@ const config = {
reporter: [
['list'],
['junit', { outputFile: 'test-results/results.xml' }],
['allure-playwright']
]
};

View File

@@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify branding related components.
*/
const { test, expect } = require('@playwright/test');
test.describe('Branding tests', () => {
test('About Modal launches with basic branding properties', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click About button
await page.click('.l-shell__app-logo');
// Verify that the NASA Logo Appears
await expect(await page.locator('.c-about__image')).toBeVisible();
// Modify the Build information in 'about' Modal
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
await expect(versionInformationLocator).toBeEnabled();
await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
await expect.soft(versionInformationLocator).toContainText(/Branch: ./);
});
test('Verify Links in About Modal', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click About button
await page.click('.l-shell__app-logo');
// Verify that clicking on the third party licenses information opens up another tab on licenses url
const [page2] = await Promise.all([
page.waitForEvent('popup'),
page.locator('text=click here for third party licensing information').click()
]);
expect(page2.waitForURL('**\/licenses**')).toBeTruthy();
});
});

View File

@@ -0,0 +1,62 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify the basic operations surrounding the example event generator.
*/
const { test, expect } = require('@playwright/test');
test.describe('Example Event Generator Operations', () => {
test('Can create example event generator with a name', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// let's make an event generator
await page.locator('button:has-text("Create")').click();
// Click li:has-text("Event Message Generator")
await page.locator('li:has-text("Event Message Generator")').click();
// Click text=Properties Title Notes >> input[type="text"]
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
// Fill text=Properties Title Notes >> input[type="text"]
await page.locator('text=Properties Title Notes >> input[type="text"]').fill('Test Event Generator');
// Press Enter
await page.locator('text=Properties Title Notes >> input[type="text"]').press('Enter');
// Click text=OK
await Promise.all([
page.waitForNavigation({ url: /.*&view=table/ }),
page.locator('text=OK').click()
]);
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Test Event Generator');
// Click button:has-text("Fixed Timespan")
await page.locator('button:has-text("Fixed Timespan")').click();
});
test.fixme('telemetry is coming in for test event', async ({ page }) => {
// Go to object created in step one
// Verify the telemetry table is filled with > 1 row
});
test.fixme('telemetry is sorted by time ascending', async ({ page }) => {
// Go to object created in step one
// Verify the telemetry table has a class with "is-sorting asc"
});
});

View File

@@ -0,0 +1,161 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
const { test, expect } = require('@playwright/test');
test.describe('Sine Wave Generator', () => {
test('Create new Sine Wave Generator Object and validate create Form Logic', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click Sine Wave Generator
await page.click('text=Sine Wave Generator');
// Verify that the each required field has required indicator
// Title
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req']);
// Verify that the Notes row does not have a required indicator
await expect(page.locator('.c-form__section div:nth-child(3) .form-row .c-form-row__state-indicator')).not.toContain('.req');
// Period
await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Amplitude
await expect(page.locator('.c-form__section div:nth-child(5) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Offset
await expect(page.locator('.c-form__section div:nth-child(6) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Data Rate
await expect(page.locator('.c-form__section div:nth-child(7) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Phase
await expect(page.locator('.c-form__section div:nth-child(8) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Randomness
await expect(page.locator('.c-form__section div:nth-child(9) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req']);
// Verify that by removing value from required text field shows invalid indicator
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('');
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req invalid']);
// Verify that by adding value to empty required text field changes invalid to valid indicator
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('non empty');
await expect(page.locator('.c-form-row__state-indicator').first()).toHaveClass(['c-form-row__state-indicator req valid']);
// Verify that by removing value from required number field shows invalid indicator
await page.locator('.field.control.l-input-sm input').first().fill('');
await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req invalid']);
// Verify that by adding value to empty required number field changes invalid to valid indicator
await page.locator('.field.control.l-input-sm input').first().fill('3');
await expect(page.locator('.c-form__section div:nth-child(4) .form-row .c-form-row__state-indicator')).toHaveClass(['c-form-row__state-indicator req valid']);
// Verify that can change value of number field by up/down arrows keys
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Press ArrowUp 3 times to change value from 3 to 6
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
await page.locator('.field.control.l-input-sm input').first().press('ArrowUp');
const value = await page.locator('.field.control.l-input-sm input').first().inputValue();
await expect(value).toBe('6');
// Click .c-form-row__state-indicator.grows
await page.locator('.c-form-row__state-indicator.grows').click();
// Click text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').click();
// Click .c-form-row__state-indicator >> nth=0
await page.locator('.c-form-row__state-indicator').first().click();
// Fill text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]
await page.locator('text=Properties Title Notes Period Amplitude Offset Data Rate (hz) Phase (radians) Ra >> input[type="text"]').fill('New Sine Wave Generator');
// Double click div:nth-child(4) .form-row .c-form-row__controls
await page.locator('div:nth-child(4) .form-row .c-form-row__controls').dblclick();
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Click div:nth-child(4) .form-row .c-form-row__state-indicator
await page.locator('div:nth-child(4) .form-row .c-form-row__state-indicator').click();
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Click .field.control.l-input-sm input >> nth=0
await page.locator('.field.control.l-input-sm input').first().click();
// Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click();
// Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click();
// Click div:nth-child(5) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click();
// Click div:nth-child(6) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').click();
// Double click div:nth-child(7) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').dblclick();
// Click div:nth-child(7) .form-row .c-form-row__state-indicator
await page.locator('div:nth-child(7) .form-row .c-form-row__state-indicator').click();
// Click div:nth-child(7) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').click();
// Fill div:nth-child(7) .form-row .c-form-row__controls .form-control .field input
await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').fill('3');
//Click text=OK
await Promise.all([
page.waitForNavigation(),
page.click('text=OK')
]);
// Verify that the Sine Wave Generator is displayed and correct
// Verify object properties
await expect(page.locator('.l-browse-bar__object-name')).toContainText('New Sine Wave Generator');
// Verify canvas rendered
await page.locator('canvas').nth(1).click({
position: {
x: 341,
y: 28
}
});
});
});

View File

@@ -19,32 +19,24 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
describe("the plugin", () => {
const NON_EDITABLE_FOLDER_KEY = 'noneditable.folder';
let openmct;
/*
This test suite is dedicated to tests which verify the basic operations surrounding moving objects.
*/
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(openmct.plugins.NonEditableFolder());
const { test, expect } = require('@playwright/test');
openmct.on('start', done);
openmct.startHeadless();
test.describe('Move item tests', () => {
test.fixme('Create a basic object and verify that it can be moved to another Folder', async ({ page }) => {
//Create and save Folder
//Create and save Domain Object
//Verify that the newly created domain object can be moved to Folder from Step 1.
//Verify that newly moved object appears in the correct point in Tree
//Verify that newly moved object appears correctly in Inspector panel
});
afterEach(() => {
return resetApplicationState(openmct);
test.fixme('Create a basic object and verify that it cannot be moved to object without Composition Provider', async ({ page }) => {
//Create and save Telemetry Object
//Create and save Domain Object
//Verify that the newly created domain object cannot be moved to Telemetry Object from step 1.
});
it('adds the new non-editable folder type', () => {
const type = openmct.types.get(NON_EDITABLE_FOLDER_KEY);
expect(type).toBeDefined();
expect(type.definition.creatable).toBeFalse();
});
});

View File

@@ -0,0 +1,27 @@
(function () {
document.addEventListener('DOMContentLoaded', () => {
const PERSISTENCE_KEY = 'persistence-tests';
const openmct = window.openmct;
openmct.objects.addRoot({
namespace: PERSISTENCE_KEY,
key: PERSISTENCE_KEY
});
openmct.objects.addProvider(PERSISTENCE_KEY, {
get(identifier) {
if (identifier.key !== PERSISTENCE_KEY) {
return undefined;
} else {
return Promise.resolve({
identifier,
type: 'folder',
name: 'Persistence Testing',
location: 'ROOT',
composition: []
});
}
}
});
});
}());

View File

@@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
*/
const { test, expect } = require('@playwright/test');
const path = require('path');
// https://github.com/nasa/openmct/issues/4323#issuecomment-1067282651
test.describe('Persistence operations', () => {
// add non persistable root item
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({ path: path.join(__dirname, 'addNoneditableObject.js') });
});
test('Persistability should be respected in the create form location field', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click the Create button
await page.click('button:has-text("Create")');
// Click text=Condition Set
await page.click('text=Condition Set');
// Click form[name="mctForm"] >> text=Persistence Testing
await page.locator('form[name="mctForm"] >> text=Persistence Testing').click();
// Check that "OK" button is disabled
const okButton = page.locator('button:has-text("OK")');
await expect(okButton).toBeDisabled();
});
test('Non-persistable objects should not show persistence related actions', async ({ page }) => {
// Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
// Click text=Persistence Testing >> nth=0
await page.locator('text=Persistence Testing').first().click({
button: 'right'
});
const menuOptions = page.locator('.c-menu ul');
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);
});
test.fixme('Cannot move a previously created domain object to non-peristable boject in Move Modal', async ({ page }) => {
//Create a domain object
//Save Domain object
//Move Object and verify that cannot select non-persistable object
//Move Object to My Items
//Verify successful move
});
});

View File

@@ -0,0 +1,48 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, 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.
*****************************************************************************/
/*
This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON.
*/
const { test, expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
test.fixme('Create a basic object and verify that it can be exported as JSON from Tree', async ({ page }) => {
//Create domain object
//Save Domain Object
//Verify that the newly created domain object can be exported as JSON from the Tree
});
test.fixme('Create a basic object and verify that it can be exported as JSON from 3 dot menu', async ({ page }) => {
//Create domain object
//Save Domain Object
//Verify that the newly created domain object can be exported as JSON from the 3 dot menu
});
test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => {
// Create 2 objects with hierarchy
// Export as JSON
// Verify Hiearchy
});
test.fixme('Verify that the ExportAsJSON dropdown does not appear for the item X', async ({ page }) => {
// Other than non-persistible objects
});
});

View File

@@ -20,14 +20,27 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
export default function () {
return function (openmct) {
openmct.types.addType("noneditable.folder", {
name: "Non-Editable Folder",
key: "noneditable.folder",
description: "Create folders to organize other objects or links to objects without the ability to edit it's properties.",
cssClass: "icon-folder",
creatable: false
});
};
}
/*
This test suite is dedicated to tests which verify the basic operations surrounding importAsJSON.
*/
const { test, expect } = require('@playwright/test');
test.describe('ExportAsJSON', () => {
test.fixme('Verify that domain object can be importAsJSON from Tree', async ({ page }) => {
//Verify that an testdata JSON file can be imported from Tree
//Verify correctness of imported domain object
});
test.fixme('Verify that domain object can be importAsJSON from 3 dot menu on folder', async ({ page }) => {
//Verify that an testdata JSON file can be imported from 3 dot menu on folder domain object
//Verify correctness of imported domain object
});
test.fixme('Verify that a nested Objects can be importAsJSON', async ({ page }) => {
// Testdata with hierarchy
// ImportAsJSON on Tree
// Verify Hierarchy
});
test.fixme('Verify that the ImportAsJSON dropdown does not appear for the item X', async ({ page }) => {
// Other than non-persistible objects
});
});

View File

@@ -26,8 +26,8 @@ This test suite is dedicated to tests which verify the basic operations surround
const { test, expect } = require('@playwright/test');
test.describe('condition set', () => {
test('create new button `condition set` creates new condition object', async ({ page }) => {
test.describe('Condition Set Operations', () => {
test('Create new button `condition set` creates new condition object', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
@@ -45,4 +45,21 @@ test.describe('condition set', () => {
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
});
test.fixme('condition set object properties exist', async ({ page }) => {
//Go to object created in step one
//Verify the Condition Set properties persist on Save
//Verify the Condition Set properties persist on page.reload()
});
test.fixme('condition set object can be modified', async ({ page }) => {
//Go to object created in step one
//Update the Condition Set properties
//Verify the Condition Set properties persist on Save
//Verify the Condition Set properties persist on page.reload()
});
test.fixme('condition set object can be deleted', async ({ page }) => {
//Go to object created in step one
//Verify that Condition Set object can be deleted
//Verify the Condition Set object does not exist in Tree
//Verify the Condition Set object does not exist with direct navigation to object's URL
});
});

View File

@@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2022, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { test, expect } = require('@playwright/test');
test.describe('Time counductor operations', () => {
test('validate start time does not exceeds end time', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4);
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
const startTimeLocator = page.locator('input[type="text"]').first();
const endTimeLocator = page.locator('input[type="text"]').nth(1);
// Click start time
await startTimeLocator.click();
// Click end time
await endTimeLocator.click();
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.fill(startDate.toString());
// invalid start date
startDate = (year + 1) + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
await endTimeLocator.click();
const startDateValidityStatus = await startTimeLocator.evaluate((element) => element.checkValidity());
expect(startDateValidityStatus).not.toBeTruthy();
// fix to valid start date
startDate = (year - 1) + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
// invalid end date
endDate = (year - 2) + endDate.substring(4);
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.click();
const endDateValidityStatus = await endTimeLocator.evaluate((element) => element.checkValidity());
expect(endDateValidityStatus).not.toBeTruthy();
});
});

View File

@@ -111,3 +111,63 @@ test('Visual - Default Condition Widget', async ({ page }) => {
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Condition Widget');
});
test('Visual - Time Conductor start time is less than end time', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
const year = new Date().getFullYear();
let startDate = 'xxxx-01-01 01:00:00.000Z';
startDate = year + startDate.substring(4);
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().fill(startDate.toString());
// verify no error msg
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Time conductor');
startDate = (year + 1) + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
await page.locator('input[type="text"]').nth(1).click();
// verify error msg for start time (unable to capture snapshot of popup)
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Start time error');
startDate = (year - 1) + startDate.substring(4);
await page.locator('input[type="text"]').first().fill(startDate.toString());
endDate = (year - 2) + endDate.substring(4);
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().click();
// verify error msg for end time (unable to capture snapshot of popup)
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'End time error');
});
test('Visual - Sine Wave Generator Form', async ({ page }) => {
//Go to baseURL
await page.goto('/', { waitUntil: 'networkidle' });
//Click the Create button
await page.click('button:has-text("Create")');
// Click text=Sine Wave Generator
await page.click('text=Sine Wave Generator');
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'Default Sine Wave Generator Form');
await page.locator('.field.control.l-input-sm input').first().click();
await page.locator('.field.control.l-input-sm input').first().fill('');
// Validate red x mark
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
await percySnapshot(page, 'removed amplitude property value');
});

View File

@@ -33,7 +33,7 @@ class EventTelemetryProvider {
generateData(firstObservedTime, count, startTime, duration, name) {
const millisecondsSinceStart = startTime - firstObservedTime;
const utc = Math.floor(startTime / duration) * duration;
const utc = startTime + (count * duration);
const ind = count % messages.length;
const message = messages[ind] + " - [" + millisecondsSinceStart + "]";
@@ -75,7 +75,7 @@ class EventTelemetryProvider {
const duration = domainObject.telemetry.duration * 1000;
const size = options.size ? options.size : this.defaultSize;
const data = [];
const firstObservedTime = Date.now();
const firstObservedTime = options.start;
let count = 0;
if (options.strategy === 'latest' || options.size === 1) {
@@ -83,7 +83,7 @@ class EventTelemetryProvider {
}
while (start <= end && data.length < size) {
const startTime = Date.now() + count;
const startTime = options.start + count;
data.push(this.generateData(firstObservedTime, count, startTime, duration, domainObject.name));
start += duration;
count += 1;

View File

@@ -35,6 +35,7 @@ describe('the plugin', () => {
telemetry: {
duration: 0
},
options: {},
type: 'eventGenerator'
};
@@ -61,7 +62,13 @@ describe('the plugin', () => {
});
});
it("supports requests", async () => {
it("supports requests without start/end defined", async () => {
const telemetry = await openmct.telemetry.request(mockDomainObject);
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
});
it("supports requests with arbitrary start time in the past", async () => {
mockDomainObject.options.start = 100000000000; // Mar 03 1973
const telemetry = await openmct.telemetry.request(mockDomainObject);
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
});

View File

@@ -26,7 +26,7 @@ import {
} from '../../src/utils/testing';
import ExampleUserProvider from './ExampleUserProvider';
describe("The Example User Plugin", () => {
xdescribe("The Example User Plugin", () => {
let openmct;
beforeEach(() => {

View File

@@ -190,7 +190,7 @@
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
openmct.install(openmct.plugins.ObjectMigration());
openmct.install(openmct.plugins.ClearData(
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'example.imagery'],
{indicator: true}
));
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));

View File

@@ -1,3 +1,2 @@
const testsContext = require.context('.', true, /\/(src|platform|\.\/example)\/.*Spec.js$/);
const testsContext = require.context('.', true, /^\.\/(src|example)\/.*Spec.js$/);
testsContext.keys().forEach(testsContext);

View File

@@ -22,29 +22,9 @@
/*global module,process*/
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['spec', 'junit'];
if (coverageEnabled) {
reporters.push('coverage-istanbul');
}
module.exports = (config) => {
const webpackConfig = require('./webpack.dev.js');
const webpackConfig = require('./webpack.coverage.js');
delete webpackConfig.output;
if (coverageEnabled) {
webpackConfig.module.rules.push({
test: /\.js$/,
exclude: /node_modules|e2e|example|lib|dist|\.*.*Spec\.js/,
use: {
loader: 'istanbul-instrumenter-loader',
options: {
esModules: true
}
}
});
}
config.set({
basePath: '',
@@ -61,8 +41,8 @@ module.exports = (config) => {
}
],
port: 9876,
reporters: reporters,
browsers: browsers,
reporters: ['spec', 'junit', 'coverage-istanbul'],
browsers: [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'],
client: {
jasmine: {
random: false,
@@ -78,17 +58,27 @@ module.exports = (config) => {
FirefoxESR: {
base: 'FirefoxHeadless',
name: 'FirefoxESR'
},
PlaywrightChrome: {
base: 'ChromiumHeadless'
},
PlaywrightSafari: {
base: 'WebKitHeadless'
},
PlaywrightChromeCanary: {
base: 'ChromiumHeadless',
launchOptions: {
channel: 'chrome-canary'
}
},
PlaywrightFirefox: {
base: 'FirefoxHeadless',
debug: true
}
},
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
// HTML test reporting.
// htmlReporter: {
// outputDir: "dist/reports/tests",
// preserveDescribeNesting: true,
// foldAll: false
// },
junitReporter: {
outputDir: "dist/reports/tests",
outputFile: "test-results.xml",
@@ -96,9 +86,7 @@ module.exports = (config) => {
},
coverageIstanbulReporter: {
fixWebpackSourcePaths: true,
dir: process.env.CIRCLE_ARTIFACTS
? process.env.CIRCLE_ARTIFACTS + '/coverage'
: "dist/reports/coverage",
dir: "dist/reports/coverage",
reports: ['lcovonly', 'text-summary'],
thresholds: {
global: {
@@ -115,6 +103,10 @@ module.exports = (config) => {
showSpecTiming: true,
failFast: false
},
plugins: [
'karma-*',
'@onslip/karma-playwright-launcher'
],
preprocessors: {
'indexTest.js': ['webpack', 'sourcemap']
},

View File

@@ -1,37 +1,40 @@
{
"name": "openmct",
"version": "2.0.0",
"version": "2.0.1-SNAPSHOT",
"description": "The Open MCT core platform",
"devDependencies": {
"@braintree/sanitize-url": "5.0.2",
"@percy/cli": "1.0.0-beta.73",
"@babel/eslint-parser": "7.16.3",
"@braintree/sanitize-url": "6.0.0",
"@onslip/karma-playwright-launcher": "0.2.1",
"@percy/cli": "1.0.0-beta.75",
"@percy/playwright": "1.0.1",
"@playwright/test": "1.18.1",
"allure-playwright": "2.0.0-beta.14",
"babel-eslint": "10.1.0",
"@playwright/test": "1.19.2",
"allure-playwright": "2.0.0-beta.15",
"babel-loader": "8.2.3",
"babel-plugin-istanbul": "6.1.1",
"comma-separated-values": "3.6.4",
"copy-webpack-plugin": "10.2.0",
"core-js": "3.20.3",
"cross-env": "6.0.3",
"core-js": "3.21.1",
"cross-env": "7.0.3",
"css-loader": "4.0.0",
"d3-axis": "1.0.x",
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",
"eslint": "7.0.0",
"eslint": "8.11.0",
"eslint-plugin-compat": "4.0.2",
"eslint-plugin-playwright": "0.8.0",
"eslint-plugin-vue": "7.5.0",
"eslint-plugin-vue": "8.5.0",
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
"eventemitter3": "1.2.0",
"exports-loader": "0.7.0",
"express": "4.13.1",
"file-loader": "6.1.0",
"file-saver": "1.3.8",
"git-rev-sync": "1.4.0",
"file-loader": "6.2.0",
"file-saver": "2.0.5",
"git-rev-sync": "3.0.2",
"html-loader": "0.5.5",
"html2canvas": "1.4.1",
"imports-loader": "0.8.0",
"istanbul-instrumenter-loader": "^3.0.1",
"jasmine-core": "4.0.0",
"jasmine-core": "4.0.1",
"jsdoc": "3.5.5",
"karma": "6.3.15",
"karma-chrome-launcher": "3.1.0",
@@ -43,37 +46,36 @@
"karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.33",
"karma-webpack": "^5.0.0",
"location-bar": "^3.0.1",
"lodash": "^4.17.12",
"karma-webpack": "5.0.0",
"location-bar": "3.0.1",
"lodash": "4.17.12",
"mini-css-extract-plugin": "2.4.5",
"moment": "2.25.3",
"moment-duration-format": "^2.2.2",
"moment": "2.29.1",
"moment-duration-format": "2.2.2",
"moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3",
"painterro": "^1.2.56",
"playwright": "^1.18.1",
"plotly.js-basic-dist": "^2.5.0",
"plotly.js-gl2d-dist": "^2.5.0",
"printj": "^1.2.1",
"raw-loader": "^0.5.1",
"request": "^2.69.0",
"node-bourbon": "4.2.3",
"painterro": "1.2.56",
"plotly.js-basic-dist": "2.5.0",
"plotly.js-gl2d-dist": "2.5.0",
"printj": "1.3.1",
"raw-loader": "4.0.2",
"request": "2.69.0",
"resolve-url-loader": "4.0.0",
"sass": "1.49.0",
"sass-loader": "12.4.0",
"sinon": "13.0.1",
"style-loader": "^1.0.1",
"uuid": "^3.3.3",
"vue": "2.5.6",
"uuid": "3.3.3",
"vue": "2.6.14",
"vue-eslint-parser": "8.2.0",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.5.6",
"webpack": "5.67.0",
"vue-template-compiler": "2.6.14",
"webpack": "5.68.0",
"webpack-cli": "4.9.2",
"webpack-dev-middleware": "^3.1.3",
"webpack-hot-middleware": "^2.22.3",
"webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.22.3",
"webpack-merge": "5.8.0",
"zepto": "^1.2.0"
"zepto": "1.2.0"
},
"scripts": {
"clean": "rm -rf ./dist ./node_modules; rm package-lock.json",
@@ -83,13 +85,19 @@
"lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
"build:prod": "cross-env webpack --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"build:coverage": "webpack --config webpack.coverage.js",
"build:watch": "webpack --config webpack.dev.js --watch",
"info": "npx envinfo --system --browsers --npmPackages --binaries --languages --markdown",
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
"test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
"test:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition.e2e",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js",
"test:coverage:pw": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run --browsers=PlaywrightChrome",
"test:coverage:pwsafari": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run --browsers=PlaywrightSafari",
"test:coverage:pwcanary": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run --browsers=PlaywrightChromeCanary",
"test:coverage:pwfirefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=PlaywrightFirefox",
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor",
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js default",
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
@@ -105,8 +113,15 @@
"url": "https://github.com/nasa/openmct.git"
},
"engines": {
"node": ">=12.20.1 <15.0.0"
"node": ">=12.22.0"
},
"browserslist": [
"Firefox ESR",
"not IE 11",
"last 2 Chrome versions",
"unreleased Chrome versions",
"ios_saf > 15"
],
"author": "",
"license": "Apache-2.0",
"private": true

View File

@@ -269,7 +269,6 @@ define([
this.install(this.plugins.ViewDatumAction());
this.install(this.plugins.ViewLargeAction());
this.install(this.plugins.ObjectInterceptors());
this.install(this.plugins.NonEditableFolder());
this.install(this.plugins.DeviceClassifier());
this.install(this.plugins.UserIndicator());
}

View File

@@ -26,45 +26,52 @@
<div class="c-overlay__dialog-title">{{ model.title }}</div>
<div class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
</div>
<form name="mctForm"
class="c-form__contents"
autocomplete="off"
@submit.prevent
<form
name="mctForm"
class="c-form__contents"
autocomplete="off"
@submit.prevent
>
<div v-for="section in formSections"
:key="section.id"
class="c-form__section"
:class="section.cssClass"
<div
v-for="section in formSections"
:key="section.id"
class="c-form__section"
:class="section.cssClass"
>
<h2 v-if="section.name"
<h2
v-if="section.name"
class="c-form__section-header"
>
{{ section.name }}
</h2>
<div v-for="(row, index) in section.rows"
:key="row.id"
class="u-contents"
<div
v-for="(row, index) in section.rows"
:key="row.id"
class="u-contents"
>
<FormRow :css-class="section.cssClass"
:first="index < 1"
:row="row"
@onChange="onChange"
<FormRow
:css-class="section.cssClass"
:first="index < 1"
:row="row"
@onChange="onChange"
/>
</div>
</div>
</form>
<div class="mct-form__controls c-overlay__button-bar c-form__bottom-bar">
<button tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
@click="onSave"
<button
tabindex="0"
:disabled="isInvalid"
class="c-button c-button--major"
@click="onSave"
>
{{ submitLabel }}
</button>
<button tabindex="0"
class="c-button"
@click="onDismiss"
<button
tabindex="0"
class="c-button"
@click="onDismiss"
>
{{ cancelLabel }}
</button>

View File

@@ -21,21 +21,25 @@
*****************************************************************************/
<template>
<div class="form-row c-form__row"
:class="[{ 'first': first }]"
@onChange="onChange"
<div
class="form-row c-form__row"
:class="[{ 'first': first }]"
@onChange="onChange"
>
<div class="c-form-row__label"
:title="row.description"
<div
class="c-form-row__label"
:title="row.description"
>
{{ row.name }}
</div>
<div class="c-form-row__state-indicator"
:class="rowClass"
<div
class="c-form-row__state-indicator"
:class="rowClass"
>
</div>
<div v-if="row.control"
class="c-form-row__controls"
<div
v-if="row.control"
class="c-form-row__controls"
>
<div ref="rowElement"></div>
</div>

View File

@@ -22,20 +22,24 @@
<template>
<div class="form-control autocomplete">
<input v-model="field"
class="autocompleteInput"
type="text"
@click="inputClicked()"
@keydown="keyDown($event)"
<input
v-model="field"
class="autocompleteInput"
type="text"
@click="inputClicked()"
@keydown="keyDown($event)"
>
<span class="icon-arrow-down"
@click="arrowClicked()"
<span
class="icon-arrow-down"
@click="arrowClicked()"
></span>
<div class="autocompleteOptions"
@blur="hideOptions = true"
<div
class="autocompleteOptions"
@blur="hideOptions = true"
>
<ul v-if="!hideOptions">
<li v-for="opt in filteredOptions"
<li
v-for="opt in filteredOptions"
:key="opt.optionId"
:class="{'optionPreSelected': optionIndex === opt.optionId}"
@click="fillInputWithString(opt.name)"

View File

@@ -22,10 +22,11 @@
<template>
<div class="c-form-control--clock-display-format-fields">
<SelectField v-for="item in items"
:key="item.key"
:model="item"
@onChange="onChange"
<SelectField
v-for="item in items"
:key="item.key"
:model="item"
@onChange="onChange"
/>
</div>
</template>

View File

@@ -22,12 +22,13 @@
<template>
<span>
<CompositeItem v-for="(item, index) in model.items"
:key="item.name"
:first="index < 1"
:value="JSON.stringify(model.value[index])"
:item="item"
@onChange="onChange"
<CompositeItem
v-for="(item, index) in model.items"
:key="item.name"
:first="index < 1"
:value="JSON.stringify(model.value[index])"
:item="item"
@onChange="onChange"
/>
</span>
</template>

View File

@@ -22,10 +22,11 @@
<template>
<div :class="compositeCssClass">
<FormRow :css-class="item.cssClass"
:first="first"
:row="row"
@onChange="onChange"
<FormRow
:css-class="item.cssClass"
:first="first"
:row="row"
@onChange="onChange"
/>
<span class="composite-control-label">
{{ item.name }}

View File

@@ -27,50 +27,55 @@
<div class="hint time sm">Min</div>
<div class="hint time sm">Sec</div>
<div class="hint timezone">Timezone</div>
<form ref="dateTimeForm"
prevent
class="u-contents"
<form
ref="dateTimeForm"
prevent
class="u-contents"
>
<div class="field control date">
<input v-model="date"
:pattern="/\d{4}-\d{2}-\d{2}/"
:placeholder="format"
type="date"
name="date"
@change="onChange"
<input
v-model="date"
:pattern="/\d{4}-\d{2}-\d{2}/"
:placeholder="format"
type="date"
name="date"
@change="onChange"
>
</div>
<div class="field control hour sm">
<input v-model="hour"
:pattern="/\d+/"
type="number"
name="hour"
maxlength="10"
min="0"
max="23"
@change="onChange"
<input
v-model="hour"
:pattern="/\d+/"
type="number"
name="hour"
maxlength="10"
min="0"
max="23"
@change="onChange"
>
</div>
<div class="field control min sm">
<input v-model="min"
:pattern="/\d+/"
type="number"
name="min"
maxlength="2"
min="0"
max="59"
@change="onChange"
<input
v-model="min"
:pattern="/\d+/"
type="number"
name="min"
maxlength="2"
min="0"
max="59"
@change="onChange"
>
</div>
<div class="field control sec sm">
<input v-model="sec"
:pattern="/\d+/"
type="number"
name="sec"
maxlength="2"
min="0"
max="59"
@change="onChange"
<input
v-model="sec"
:pattern="/\d+/"
type="number"
name="sec"
maxlength="2"
min="0"
max="59"
@change="onChange"
>
</div>
<div class="field control timezone">

View File

@@ -22,18 +22,21 @@
<template>
<span class="form-control shell">
<span class="field control"
:class="model.cssClass"
<span
class="field control"
:class="model.cssClass"
>
<input id="fileElem"
ref="fileInput"
type="file"
accept=".json"
style="display:none"
<input
id="fileElem"
ref="fileInput"
type="file"
accept=".json"
style="display:none"
>
<button id="fileSelect"
class="c-button"
@click="selectFile"
<button
id="fileSelect"
class="c-button"
@click="selectFile"
>
{{ name }}
</button>

View File

@@ -22,21 +22,25 @@
<template>
<span class="form-control shell">
<span class="field control"
:class="model.cssClass"
<span
class="field control"
:class="model.cssClass"
>
<input v-model="field"
type="number"
:min="model.min"
:max="model.max"
:step="model.step"
@blur="blur()"
<input
v-model="field"
type="number"
:min="model.min"
:max="model.max"
:step="model.step"
@input="updateText()"
>
</span>
</span>
</template>
<script>
import { throttle } from 'lodash';
export default {
props: {
model: {
@@ -49,8 +53,12 @@ export default {
field: this.model.value
};
},
mounted() {
this.updateText = throttle(this.updateText.bind(this), 200);
},
methods: {
blur() {
updateText() {
console.log('updateText', this.field);
const data = {
model: this.model,
value: this.field

View File

@@ -22,14 +22,16 @@
<template>
<div class="form-control select-field">
<select v-model="selected"
required="model.required"
name="mctControl"
@change="onChange($event)"
<select
v-model="selected"
required="model.required"
name="mctControl"
@change="onChange($event)"
>
<option v-for="option in model.options"
:key="option.name"
:value="option.value"
<option
v-for="option in model.options"
:key="option.name"
:value="option.value"
>
{{ option.name }}
</option>

View File

@@ -22,13 +22,15 @@
<template>
<span class="form-control shell">
<span class="field control"
:class="model.cssClass"
<span
class="field control"
:class="model.cssClass"
>
<textarea v-model="field"
type="text"
:size="model.size"
@blur="blur()"
<textarea
v-model="field"
type="text"
:size="model.size"
@input="updateText()"
>
</textarea>
</span>
@@ -36,6 +38,8 @@
</template>
<script>
import { throttle } from 'lodash';
export default {
props: {
model: {
@@ -48,8 +52,11 @@ export default {
field: this.model.value
};
},
mounted() {
this.updateText = throttle(this.updateText.bind(this), 500);
},
methods: {
blur() {
updateText() {
const data = {
model: this.model,
value: this.field

View File

@@ -22,19 +22,23 @@
<template>
<span class="form-control shell">
<span class="field control"
:class="model.cssClass"
<span
class="field control"
:class="model.cssClass"
>
<input v-model="field"
type="text"
:size="model.size"
@blur="blur()"
<input
v-model="field"
type="text"
:size="model.size"
@input="updateText()"
>
</span>
</span>
</template>
<script>
import { throttle } from 'lodash';
export default {
props: {
model: {
@@ -47,8 +51,11 @@ export default {
field: this.model.value
};
},
mounted() {
this.updateText = throttle(this.updateText.bind(this), 500);
},
methods: {
blur() {
updateText() {
const data = {
model: this.model,
value: this.field

View File

@@ -1,6 +1,7 @@
<template>
<div class="c-menu"
:class="options.menuClass"
<div
class="c-menu"
:class="options.menuClass"
>
<ul v-if="options.actions.length && options.actions[0].length">
<template

View File

@@ -1,8 +1,10 @@
<template>
<div class="c-menu"
:class="[options.menuClass, 'c-super-menu']"
<div
class="c-menu"
:class="[options.menuClass, 'c-super-menu']"
>
<ul v-if="options.actions.length && options.actions[0].length"
<ul
v-if="options.actions.length && options.actions[0].length"
class="c-super-menu__menu"
>
<template
@@ -34,7 +36,8 @@
</template>
</ul>
<ul v-else
<ul
v-else
class="c-super-menu__menu"
>
<li

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
import CSV from 'comma-separated-values';
import {saveAs} from 'file-saver/FileSaver';
import {saveAs} from 'saveAs';
class CSVExporter {
export(rows, options) {

View File

@@ -31,7 +31,7 @@ function replaceDotsWithUnderscores(filename) {
return filename.replace(regex, '_');
}
import {saveAs} from 'file-saver/FileSaver';
import {saveAs} from 'saveAs';
import html2canvas from 'html2canvas';
import uuid from 'uuid';

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import {saveAs} from 'file-saver/FileSaver';
import {saveAs} from 'saveAs';
class JSONExporter {
export(obj, options) {

View File

@@ -1,28 +1,34 @@
<template>
<div ref="plotWrapper"
class="has-local-controls"
:class="{ 's-unsynced' : isZoomed }"
<div
ref="plotWrapper"
class="has-local-controls"
:class="{ 's-unsynced' : isZoomed }"
>
<div v-if="isZoomed"
class="l-state-indicators"
<div
v-if="isZoomed"
class="l-state-indicators"
>
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
<span
class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
></span>
</div>
<div ref="plot"
class="c-bar-chart"
@plotly_relayout="zoom"
<div
ref="plot"
class="c-bar-chart"
@plotly_relayout="zoom"
></div>
<div v-if="false"
ref="localControl"
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
<div
v-if="false"
ref="localControl"
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
>
<button v-if="data.length"
class="c-button icon-reset"
:disabled="!isZoomed"
title="Reset pan/zoom"
@click="reset()"
<button
v-if="data.length"
class="c-button icon-reset"
:disabled="!isZoomed"
title="Reset pan/zoom"
@click="reset()"
>
</button>
</div>

View File

@@ -21,12 +21,13 @@
-->
<template>
<BarGraph ref="barGraph"
class="c-plot c-bar-chart-view"
:data="trace"
:plot-axis-title="plotAxisTitle"
@subscribe="subscribeToAll"
@unsubscribe="removeAllSubscriptions"
<BarGraph
ref="barGraph"
class="c-plot c-bar-chart-view"
:data="trace"
:plot-axis-title="plotAxisTitle"
@subscribe="subscribeToAll"
@unsubscribe="removeAllSubscriptions"
/>
</template>

View File

@@ -22,11 +22,13 @@
<template>
<ul class="c-tree c-bar-graph-options">
<h2 title="Display properties for this object">Bar Graph Series</h2>
<li v-for="series in domainObject.composition"
<li
v-for="series in domainObject.composition"
:key="series.key"
>
<series-options :item="series"
:color-palette="colorPalette"
<series-options
:item="series"
:color-palette="colorPalette"
/>
</li>
</ul>

View File

@@ -21,12 +21,14 @@
-->
<template>
<ul>
<li class="c-tree__item menus-to-left"
<li
class="c-tree__item menus-to-left"
:class="aliasCss"
>
<span class="c-disclosure-triangle is-enabled flex-elem"
:class="expandedCssClass"
@click="expanded = !expanded"
<span
class="c-disclosure-triangle is-enabled flex-elem"
:class="expandedCssClass"
@click="expanded = !expanded"
>
</span>
@@ -36,14 +38,15 @@
<div class="c-object-label__name">{{ name }}</div>
</div>
</li>
<ColorSwatch v-if="expanded"
:current-color="currentColor"
title="Manually set the color for this bar graph series."
edit-title="Manually set the color for this bar graph series"
view-title="The color for this bar graph series."
short-label="Color"
class="grid-properties"
@colorSet="setColor"
<ColorSwatch
v-if="expanded"
:current-color="currentColor"
title="Manually set the color for this bar graph series."
edit-title="Manually set the color for this bar graph series"
view-title="The color for this bar graph series."
short-label="Color"
class="grid-properties"
@colorSet="setColor"
/>
</ul>
</template>

View File

@@ -185,10 +185,14 @@ describe('The Clear Data Plugin:', () => {
beforeEach((done) => {
openmct = createOpenMct();
clearDataPlugin = new ClearDataPlugin(
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
{indicator: true}
);
clearDataPlugin = new ClearDataPlugin([
'table',
'telemetry.plot.overlay',
'telemetry.plot.stacked',
'example.imagery'
], {
indicator: true
});
openmct.install(clearDataPlugin);
appHolder = document.createElement('div');
document.body.appendChild(appHolder);

View File

@@ -21,31 +21,35 @@
*****************************************************************************/
<template>
<div class="c-condition-h"
:class="{ 'is-drag-target': draggingOver }"
@dragover.prevent
@drop.prevent="dropCondition($event, conditionIndex)"
@dragenter="dragEnter($event, conditionIndex)"
@dragleave="dragLeave($event, conditionIndex)"
<div
class="c-condition-h"
:class="{ 'is-drag-target': draggingOver }"
@dragover.prevent
@drop.prevent="dropCondition($event, conditionIndex)"
@dragenter="dragEnter($event, conditionIndex)"
@dragleave="dragLeave($event, conditionIndex)"
>
<div class="c-condition-h__drop-target"></div>
<div v-if="isEditing"
:class="{'is-current': condition.id === currentConditionId}"
class="c-condition c-condition--edit"
<div
v-if="isEditing"
:class="{'is-current': condition.id === currentConditionId}"
class="c-condition c-condition--edit"
>
<!-- Edit view -->
<div class="c-condition__header">
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragend="dragEnd"
<span
class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
title="Drag to reorder conditions"
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
:draggable="!condition.isDefault"
@dragstart="dragStart"
@dragend="dragEnd"
></span>
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
<span
class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
<span class="c-condition__name">{{ condition.configuration.name }}</span>
@@ -54,107 +58,123 @@
Define criteria
</template>
<span v-else>
<condition-description :show-label="false"
:condition="condition"
<condition-description
:show-label="false"
:condition="condition"
/>
</span>
</span>
<div class="c-condition__buttons">
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
<button
v-if="!condition.isDefault"
class="c-click-icon c-condition__duplicate-button icon-duplicate"
title="Duplicate this condition"
@click="cloneCondition"
></button>
<button v-if="!condition.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
<button
v-if="!condition.isDefault"
class="c-click-icon c-condition__delete-button icon-trash"
title="Delete this condition"
@click="removeCondition"
></button>
</div>
</div>
<div v-if="expanded"
class="c-condition__definition c-cdef"
<div
v-if="expanded"
class="c-condition__definition c-cdef"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Condition Name</span>
<span class="c-cdef__controls">
<input v-model="condition.configuration.name"
class="t-condition-input__name"
type="text"
@change="persist"
<input
v-model="condition.configuration.name"
class="t-condition-input__name"
type="text"
@change="persist"
>
</span>
<span class="c-cdef__label">Output</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select v-model="selectedOutputSelection"
@change="setOutputValue"
<select
v-model="selectedOutputSelection"
@change="setOutputValue"
>
<option v-for="option in outputOptions"
:key="option"
:value="option"
<option
v-for="option in outputOptions"
:key="option"
:value="option"
>
{{ initCap(option) }}
</option>
</select>
</span>
<span class="c-cdef__control">
<input v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output"
class="t-condition-name-input"
type="text"
@change="persist"
<input
v-if="selectedOutputSelection === outputOptions[2]"
v-model="condition.configuration.output"
class="t-condition-name-input"
type="text"
@change="persist"
>
</span>
</span>
<div v-if="!condition.isDefault"
class="c-cdef__match-and-criteria"
<div
v-if="!condition.isDefault"
class="c-cdef__match-and-criteria"
>
<span class="c-cdef__separator c-row-separator"></span>
<span class="c-cdef__label">Match</span>
<span class="c-cdef__controls">
<select v-model="condition.configuration.trigger"
@change="persist"
<select
v-model="condition.configuration.trigger"
@change="persist"
>
<option v-for="option in triggers"
:key="option.value"
:value="option.value"
<option
v-for="option in triggers"
:key="option.value"
:value="option.value"
> {{ option.label }}</option>
</select>
</span>
<template v-if="telemetry.length || condition.configuration.criteria.length">
<div v-for="(criterion, index) in condition.configuration.criteria"
:key="criterion.id"
class="c-cdef__criteria"
<div
v-for="(criterion, index) in condition.configuration.criteria"
:key="criterion.id"
class="c-cdef__criteria"
>
<Criterion :telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="condition.configuration.trigger"
:is-default="condition.configuration.criteria.length === 1"
@persist="persist"
<Criterion
:telemetry="telemetry"
:criterion="criterion"
:index="index"
:trigger="condition.configuration.trigger"
:is-default="condition.configuration.criteria.length === 1"
@persist="persist"
/>
<div class="c-cdef__criteria__buttons">
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
<button
class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
title="Duplicate this criteria"
@click="cloneCriterion(index)"
></button>
<button v-if="!(condition.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
<button
v-if="!(condition.configuration.criteria.length === 1)"
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
title="Delete this criteria"
@click="removeCriterion(index)"
></button>
</div>
</div>
</template>
<div class="c-cdef__separator c-row-separator"></div>
<div class="c-cdef__controls"
:disabled="!telemetry.length"
<div
class="c-cdef__controls"
:disabled="!telemetry.length"
>
<button
class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
@@ -166,9 +186,10 @@
</div>
</div>
</div>
<div v-else
class="c-condition c-condition--browse"
:class="{'is-current': condition.id === currentConditionId}"
<div
v-else
class="c-condition c-condition--browse"
:class="{'is-current': condition.id === currentConditionId}"
>
<!-- Browse view -->
<div class="c-condition__header">
@@ -180,8 +201,9 @@
</span>
</div>
<div class="c-condition__summary">
<condition-description :show-label="false"
:condition="condition"
<condition-description
:show-label="false"
:condition="condition"
/>
</div>
</div>

View File

@@ -21,8 +21,9 @@
*****************************************************************************/
<template>
<section id="conditionCollection"
:class="{ 'is-expanded': expanded }"
<section
id="conditionCollection"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__header c-section__header">
<span
@@ -32,12 +33,14 @@
></span>
<div class="c-cs__header-label c-section__label">Conditions</div>
</div>
<div v-if="expanded"
class="c-cs__content"
<div
v-if="expanded"
class="c-cs__content"
>
<div v-show="isEditing"
class="hint"
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
<div
v-show="isEditing"
class="hint"
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
>
<template v-if="!telemetryObjs.length">Drag telemetry into this Condition Set to configure Conditions and add criteria.</template>
<template v-else>The first condition to match is the one that is applied. Drag conditions to reorder.</template>
@@ -52,24 +55,26 @@
<span class="c-cs-button__label">Add Condition</span>
</button>
<div class="c-cs__conditions-h"
:class="{ 'is-active-dragging': isDragging }"
<div
class="c-cs__conditions-h"
:class="{ 'is-active-dragging': isDragging }"
>
<Condition v-for="(condition, index) in conditionCollection"
:key="condition.id"
:condition="condition"
:current-condition-id="currentConditionId"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
:move-index="moveIndex"
:is-dragging="isDragging"
@updateCondition="updateCondition"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
@dragComplete="dragComplete"
@dropCondition="dropCondition"
<Condition
v-for="(condition, index) in conditionCollection"
:key="condition.id"
:condition="condition"
:current-condition-id="currentConditionId"
:condition-index="index"
:telemetry="telemetryObjs"
:is-editing="isEditing"
:move-index="moveIndex"
:is-dragging="isDragging"
@updateCondition="updateCondition"
@removeCondition="removeCondition"
@cloneCondition="cloneCondition"
@setMoveIndex="setMoveIndex"
@dragComplete="dragComplete"
@dropCondition="dropCondition"
/>
</div>
</div>

View File

@@ -22,18 +22,21 @@
<template>
<div class="c-style__condition-desc">
<span v-if="showLabel && condition"
class="c-style__condition-desc__name c-condition__name"
<span
v-if="showLabel && condition"
class="c-style__condition-desc__name c-condition__name"
>
{{ condition.configuration.name }}
</span>
<span v-if="!condition.isDefault"
class="c-style__condition-desc__text"
<span
v-if="!condition.isDefault"
class="c-style__condition-desc__text"
>
{{ description }}
</span>
<span v-else
class="c-style__condition-desc__text"
<span
v-else
class="c-style__condition-desc__text"
>
Match if no other condition is matched
</span>

View File

@@ -21,12 +21,14 @@
*****************************************************************************/
<template>
<div v-if="conditionErrors.length"
class="c-condition__errors"
<div
v-if="conditionErrors.length"
class="c-condition__errors"
>
<div v-for="(error, index) in conditionErrors"
:key="index"
class="u-alert u-alert--block u-alert--with-icon"
<div
v-for="(error, index) in conditionErrors"
:key="index"
class="u-alert u-alert--block u-alert--with-icon"
>{{ error.message.errorText }} {{ error.additionalInfo }}
</div>
</div>

View File

@@ -36,18 +36,20 @@
</div>
</section>
<div class="c-cs__test-data-and-conditions-w">
<TestData class="c-cs__test-data"
:is-editing="isEditing"
:test-data="testData"
:telemetry="telemetryObjs"
@updateTestData="updateTestData"
<TestData
class="c-cs__test-data"
:is-editing="isEditing"
:test-data="testData"
:telemetry="telemetryObjs"
@updateTestData="updateTestData"
/>
<ConditionCollection class="c-cs__conditions"
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@telemetryUpdated="updateTelemetry"
<ConditionCollection
class="c-cs__conditions"
:is-editing="isEditing"
:test-data="testData"
@conditionSetResultUpdated="updateCurrentOutput"
@updateDefaultOutput="updateDefaultOutput"
@telemetryUpdated="updateTelemetry"
/>
</div>
</div>

View File

@@ -26,76 +26,89 @@
<span class="c-cdef__label">{{ setRowLabel }}</span>
<span class="c-cdef__controls">
<span class="c-cdef__control">
<select ref="telemetrySelect"
v-model="criterion.telemetry"
@change="updateMetadataOptions"
<select
ref="telemetrySelect"
v-model="criterion.telemetry"
@change="updateMetadataOptions"
>
<option value="">- Select Telemetry -</option>
<option value="all">all telemetry</option>
<option value="any">any telemetry</option>
<option v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
<option
v-for="telemetryOption in telemetry"
:key="telemetryOption.identifier.key"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span v-if="criterion.telemetry"
class="c-cdef__control"
<span
v-if="criterion.telemetry"
class="c-cdef__control"
>
<select ref="metadataSelect"
v-model="criterion.metadata"
@change="updateOperations"
<select
ref="metadataSelect"
v-model="criterion.metadata"
@change="updateOperations"
>
<option value="">- Select Field -</option>
<option v-for="option in telemetryMetadataOptions"
:key="option.key"
:value="option.key"
<option
v-for="option in telemetryMetadataOptions"
:key="option.key"
:value="option.key"
>
{{ option.name }}
</option>
<option value="dataReceived">any data received</option>
</select>
</span>
<span v-if="criterion.telemetry && criterion.metadata"
class="c-cdef__control"
<span
v-if="criterion.telemetry && criterion.metadata"
class="c-cdef__control"
>
<select v-model="criterion.operation"
@change="updateInputVisibilityAndValues"
<select
v-model="criterion.operation"
@change="updateInputVisibilityAndValues"
>
<option value="">- Select Comparison -</option>
<option v-for="option in filteredOps"
:key="option.name"
:value="option.name"
<option
v-for="option in filteredOps"
:key="option.name"
:value="option.name"
>
{{ option.text }}
</option>
</select>
<template v-if="!enumerations.length">
<span v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
<span
v-for="(item, inputIndex) in inputCount"
:key="inputIndex"
class="c-cdef__control__inputs"
>
<input v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@change="persist"
<input
v-model="criterion.input[inputIndex]"
class="c-cdef__control__input"
:type="setInputType"
@change="persist"
>
<span v-if="inputIndex < inputCount-1">and</span>
</span>
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
</template>
<span v-else>
<span v-if="inputCount && criterion.operation"
class="c-cdef__control"
<span
v-if="inputCount && criterion.operation"
class="c-cdef__control"
>
<select v-model="criterion.input[0]"
@change="persist"
<select
v-model="criterion.input[0]"
@change="persist"
>
<option v-for="option in enumerations"
:key="option.string"
:value="option.value.toString()"
<option
v-for="option in enumerations"
:key="option.string"
:value="option.value.toString()"
>
{{ option.string }}
</option>

View File

@@ -21,9 +21,10 @@
*****************************************************************************/
<template>
<section v-show="isEditing"
id="test-data"
:class="{ 'is-expanded': expanded }"
<section
v-show="isEditing"
id="test-data"
:class="{ 'is-expanded': expanded }"
>
<div class="c-cs__header c-section__header">
<span
@@ -33,11 +34,13 @@
></span>
<div class="c-cs__header-label c-section__label">Test Data</div>
</div>
<div v-if="expanded"
class="c-cs__content"
<div
v-if="expanded"
class="c-cs__content"
>
<div class="c-cs__test-data__controls c-cdef__controls"
:disabled="!telemetry.length"
<div
class="c-cs__test-data__controls c-cdef__controls"
:disabled="!telemetry.length"
>
<label class="c-toggle-switch">
<input
@@ -50,59 +53,69 @@
</label>
</div>
<div class="c-cs-tests">
<span v-for="(testInput, tIndex) in testInputs"
:key="tIndex"
class="c-test-datum c-cs-test"
<span
v-for="(testInput, tIndex) in testInputs"
:key="tIndex"
class="c-test-datum c-cs-test"
>
<span class="c-cs-test__label">Set</span>
<span class="c-cs-test__controls">
<span class="c-cdef__control">
<select v-model="testInput.telemetry"
@change="updateMetadata(testInput)"
<select
v-model="testInput.telemetry"
@change="updateMetadata(testInput)"
>
<option value="">- Select Telemetry -</option>
<option v-for="(telemetryOption, index) in telemetry"
:key="index"
:value="telemetryOption.identifier"
<option
v-for="(telemetryOption, index) in telemetry"
:key="index"
:value="telemetryOption.identifier"
>
{{ telemetryOption.name }}
</option>
</select>
</span>
<span v-if="testInput.telemetry"
class="c-cdef__control"
<span
v-if="testInput.telemetry"
class="c-cdef__control"
>
<select v-model="testInput.metadata"
@change="updateTestData"
<select
v-model="testInput.metadata"
@change="updateTestData"
>
<option value="">- Select Field -</option>
<option v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
:key="index"
:value="option.key"
<option
v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
:key="index"
:value="option.key"
>
{{ option.name }}
</option>
</select>
</span>
<span v-if="testInput.metadata"
class="c-cdef__control__inputs"
<span
v-if="testInput.metadata"
class="c-cdef__control__inputs"
>
<input v-model="testInput.value"
placeholder="Enter test input"
type="text"
class="c-cdef__control__input"
@change="updateTestData"
<input
v-model="testInput.value"
placeholder="Enter test input"
type="text"
class="c-cdef__control__input"
@change="updateTestData"
>
</span>
</span>
<div class="c-cs-test__buttons">
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test datum"
@click="addTestInput(testInput)"
<button
class="c-click-icon c-test-data__duplicate-button icon-duplicate"
title="Duplicate this test datum"
@click="addTestInput(testInput)"
></button>
<button class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test datum"
@click="removeTestInput(tIndex)"
<button
class="c-click-icon c-test-data__delete-button icon-trash"
title="Delete this test datum"
@click="removeTestInput(tIndex)"
></button>
</div>
</span>

View File

@@ -23,52 +23,60 @@
<template>
<div class="c-style has-local-controls c-toolbar">
<div class="c-style__controls">
<div :class="[
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
class="c-style-thumb"
<div
:class="[
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
]"
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
class="c-style-thumb"
>
<span class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
<span
class="c-style-thumb__text"
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
>
ABC
</span>
</div>
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
<toolbar-color-picker
v-if="hasProperty(styleItem.style.border)"
class="c-style__toolbar-button--border-color u-menu-to--center"
:options="borderColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
<toolbar-color-picker
v-if="hasProperty(styleItem.style.backgroundColor)"
class="c-style__toolbar-button--background-color u-menu-to--center"
:options="backgroundColorOption"
@change="updateStyleValue"
/>
<toolbar-color-picker v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
<toolbar-color-picker
v-if="hasProperty(styleItem.style.color)"
class="c-style__toolbar-button--color u-menu-to--center"
:options="colorOption"
@change="updateStyleValue"
/>
<toolbar-button v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
<toolbar-button
v-if="hasProperty(styleItem.style.imageUrl)"
class="c-style__toolbar-button--image-url"
:options="imageUrlOption"
@change="updateStyleValue"
/>
<toolbar-toggle-button v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@change="updateStyleValue"
<toolbar-toggle-button
v-if="hasProperty(styleItem.style.isStyleInvisible)"
class="c-style__toolbar-button--toggle-visible"
:options="isStyleInvisibleOption"
@change="updateStyleValue"
/>
</div>
<!-- Save Styles -->
<toolbar-button v-if="canSaveStyle"
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
:options="saveOptions"
@click="saveItemStyle()"
<toolbar-button
v-if="canSaveStyle"
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
:options="saveOptions"
@click="saveItemStyle()"
/>
</div>
</template>

View File

@@ -22,8 +22,9 @@
<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"
<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>
@@ -37,16 +38,18 @@
@set-font-property="setFontProperty"
/>
<div class="c-inspect-styles__content">
<div v-if="staticStyle"
class="c-inspect-styles__style"
<div
v-if="staticStyle"
class="c-inspect-styles__style"
>
<StyleEditor class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle"
@save-style="saveStyle"
<StyleEditor
class="c-inspect-styles__editor"
:style-item="staticStyle"
:is-editing="allowEditing"
:mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle"
@save-style="saveStyle"
/>
</div>
<button
@@ -64,9 +67,10 @@
Conditional Object Styles
</div>
<div class="c-inspect-styles__content c-inspect-styles__condition-set c-inspect-styles__elem">
<a v-if="conditionSetDomainObject"
class="c-object-label"
@click="navigateOrPreview"
<a
v-if="conditionSetDomainObject"
class="c-object-label"
@click="navigateOrPreview"
>
<span class="c-object-label__type-icon icon-conditional"></span>
<span class="c-object-label__name">{{ conditionSetDomainObject.name }}</span>
@@ -80,15 +84,17 @@
<span class="c-button__label">Change...</span>
</button>
<button class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
<button
class="c-click-icon icon-x"
title="Remove conditional styles"
@click="removeConditionSet"
></button>
</template>
</div>
<div v-if="isConditionWidget && allowEditing"
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
<div
v-if="isConditionWidget && allowEditing"
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
>
<label class="c-toggle-switch">
<input
@@ -100,8 +106,9 @@
<span class="c-toggle-switch__label">Use Condition Set output as label</span>
</label>
</div>
<div v-if="isConditionWidget && !allowEditing"
class="c-inspect-styles__elem"
<div
v-if="isConditionWidget && !allowEditing"
class="c-inspect-styles__elem"
>
<span class="c-toggle-switch__label">Condition Set output as label:
<span v-if="useConditionSetOutputAsLabel"> Yes</span><span v-else> No</span>
@@ -114,27 +121,32 @@
@set-font-property="setFontProperty"
/>
<div v-if="conditionsLoaded"
class="c-inspect-styles__conditions"
<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)"
<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-error
:show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
<condition-description
:show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
/>
<StyleEditor class="c-inspect-styles__editor"
:style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing"
@persist="updateConditionalStyle"
@save-style="saveStyle"
<StyleEditor
class="c-inspect-styles__editor"
:style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing"
@persist="updateConditionalStyle"
@save-style="saveStyle"
/>
</div>
</div>
@@ -325,16 +337,7 @@ export default {
return item && (item.type === type);
},
canPersistObject(item) {
// for now the only way to tell if an object can be persisted is if it is creatable.
let creatable = false;
if (item) {
const type = this.openmct.types.get(item.type);
if (type && type.definition) {
creatable = (type.definition.creatable !== undefined && (type.definition.creatable === 'true' || type.definition.creatable === true));
}
}
return creatable;
return this.openmct.objects.isPersistable(item.identifier);
},
hasConditionalStyle(domainObject, layoutItem) {
const id = layoutItem ? layoutItem.id : undefined;

View File

@@ -21,9 +21,10 @@
*****************************************************************************/
<template>
<component :is="urlDefined ? 'a' : 'span'"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
<component
:is="urlDefined ? 'a' : 'span'"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
>
<div class="c-condition-widget__label">
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}

View File

@@ -37,8 +37,9 @@
:data-font="item.font"
@contextmenu.prevent="showContextMenu"
>
<div class="is-status__indicator"
:title="`This item is ${status}`"
<div
class="is-status__indicator"
:title="`This item is ${status}`"
></div>
<div
v-if="showLabel"

View File

@@ -97,13 +97,16 @@ export default class DuplicateAction {
validate(currentParent) {
return (data) => {
const parentCandidatePath = data.value;
const parentCandidate = parentCandidatePath[0];
const parentCandidate = data.value[0];
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
return false;
}
if (!parentCandidateKeystring || !currentParentKeystring) {
return false;
}
@@ -122,13 +125,14 @@ export default class DuplicateAction {
}
appliesTo(objectPath) {
let parent = objectPath[1];
let parentType = parent && this.openmct.types.get(parent.type);
let child = objectPath[0];
let childType = child && this.openmct.types.get(child.type);
let locked = child.locked ? child.locked : parent && parent.locked;
const parent = objectPath[1];
const parentType = parent && this.openmct.types.get(parent.type);
const child = objectPath[0];
const childType = child && this.openmct.types.get(child.type);
const locked = child.locked ? child.locked : parent && parent.locked;
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
if (locked) {
if (locked || !isPersistable) {
return false;
}

View File

@@ -52,7 +52,7 @@ export default class ExportAsJSONAction {
appliesTo(objectPath) {
let domainObject = objectPath[0];
return this._isCreatable(domainObject);
return this._isCreatableAndPersistable(domainObject);
}
/**
*
@@ -80,10 +80,11 @@ export default class ExportAsJSONAction {
* @param {object} domainObject
* @returns {boolean}
*/
_isCreatable(domainObject) {
_isCreatableAndPersistable(domainObject) {
const type = this.openmct.types.get(domainObject.type);
const isPersistable = this.openmct.objects.isPersistable(domainObject.identifier);
return type && type.definition.creatable;
return type && type.definition.creatable && isPersistable;
}
/**
* @private
@@ -170,7 +171,7 @@ export default class ExportAsJSONAction {
.then((children) => {
children.forEach((child, index) => {
// Only export if object is creatable
if (this._isCreatable(child)) {
if (this._isCreatableAndPersistable(child)) {
// Prevents infinite export of self-contained objs
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
// If object is a link to something absent from

View File

@@ -27,6 +27,10 @@ describe('Export as JSON plugin', () => {
it('ExportAsJSONAction applies to folder', () => {
domainObject = {
identifier: {
key: 'export-testing',
namespace: ''
},
composition: [],
location: 'mine',
modified: 1640115501237,
@@ -40,6 +44,10 @@ describe('Export as JSON plugin', () => {
it('ExportAsJSONAction applies to telemetry.plot.overlay', () => {
domainObject = {
identifier: {
key: 'export-testing',
namespace: ''
},
composition: [],
location: 'mine',
modified: 1640115501237,
@@ -53,6 +61,10 @@ describe('Export as JSON plugin', () => {
it('ExportAsJSONAction applies to telemetry.plot.stacked', () => {
domainObject = {
identifier: {
key: 'export-testing',
namespace: ''
},
composition: [],
location: 'mine',
modified: 1640115501237,
@@ -64,16 +76,24 @@ describe('Export as JSON plugin', () => {
expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true);
});
it('ExportAsJSONAction applies does not applies to non-creatable objects', () => {
it('ExportAsJSONAction does not applie to non-persistable objects', () => {
domainObject = {
identifier: {
key: 'export-testing',
namespace: ''
},
composition: [],
location: 'mine',
modified: 1640115501237,
name: 'Non Editable Folder',
persisted: 1640115501237,
type: 'noneditable.folder'
type: 'folder'
};
spyOn(openmct.objects, 'getProvider').and.callFake(() => {
return { get: () => domainObject };
});
expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(false);
});

View File

@@ -26,8 +26,9 @@
</div>
</div>
<div class="c-grid-item__controls">
<div class="is-status__indicator"
:title="`This item is ${status}`"
<div
class="is-status__indicator"
:title="`This item is ${status}`"
></div>
<div
class="icon-people"

View File

@@ -17,8 +17,9 @@
class="c-object-label__type-icon c-list-item__name__type-icon"
:class="item.type.cssClass"
>
<span class="is-status__indicator"
:title="`This item is ${status}`"
<span
class="is-status__indicator"
:title="`This item is ${status}`"
></span>
</div>
<div class="c-object-label__name c-list-item__name__name">{{ item.model.name }}</div>

View File

@@ -101,7 +101,10 @@ export default class CreateWizard {
// Ensure there is always a 'save in' section
if (includeLocation) {
function validateLocation(data) {
return self.openmct.composition.checkPolicy(data.value[0], domainObject);
const policyCheck = self.openmct.composition.checkPolicy(data.value[0], domainObject);
const parentIsPersistable = self.openmct.objects.isPersistable(data.value[0].identifier);
return policyCheck && parentIsPersistable;
}
sections.push({

View File

@@ -22,12 +22,13 @@
<template>
<a class="c-hyperlink"
:class="{
'c-hyperlink--button' : isButton
}"
:target="domainObject.linkTarget"
:href="url"
<a
class="c-hyperlink"
:class="{
'c-hyperlink--button' : isButton
}"
:target="domainObject.linkTarget"
:href="url"
>
<span class="c-hyperlink__label">{{ domainObject.displayText }}</span>
</a>

View File

@@ -21,124 +21,144 @@
*****************************************************************************/
<template>
<div ref="compassRoseWrapper"
class="w-direction-rose"
:class="compassRoseSizingClasses"
@click="toggleLockCompass"
<div
ref="compassRoseWrapper"
class="w-direction-rose"
:class="compassRoseSizingClasses"
@click="toggleLockCompass"
>
<svg ref="compassRoseSvg"
class="c-compass-rose-svg"
viewBox="0 0 100 100"
<svg
ref="compassRoseSvg"
class="c-compass-rose-svg"
viewBox="0 0 100 100"
>
<mask id="mask0"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
<mask
id="mask0"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="100"
height="100"
>
<circle cx="50"
cy="50"
r="50"
fill="black"
<circle
cx="50"
cy="50"
r="50"
fill="black"
/>
</mask>
<g class="c-cr__compass-wrapper">
<g class="c-cr__compass-main"
mask="url(#mask0)"
<g
class="c-cr__compass-main"
mask="url(#mask0)"
>
<!-- Background and clipped elements -->
<rect class="c-cr__bg"
width="100"
height="100"
fill="black"
<rect
class="c-cr__bg"
width="100"
height="100"
fill="black"
/>
<rect class="c-cr__edge"
width="100"
height="100"
fill="url(#paint0_radial)"
<rect
class="c-cr__edge"
width="100"
height="100"
fill="url(#paint0_radial)"
/>
<rect v-if="hasSunHeading"
class="c-cr__sun"
width="100"
height="100"
fill="url(#paint1_radial)"
:style="sunHeadingStyle"
<rect
v-if="hasSunHeading"
class="c-cr__sun"
width="100"
height="100"
fill="url(#paint1_radial)"
:style="sunHeadingStyle"
/>
<!-- Camera FOV -->
<mask id="mask2"
class="c-cr__cam-fov-l-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="50"
height="100"
<mask
id="mask2"
class="c-cr__cam-fov-l-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="50"
height="100"
>
<rect width="51"
height="100"
<rect
width="51"
height="100"
/>
</mask>
<mask id="mask1"
class="c-cr__cam-fov-r-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="50"
y="0"
width="50"
height="100"
<mask
id="mask1"
class="c-cr__cam-fov-r-mask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="50"
y="0"
width="50"
height="100"
>
<rect x="49"
width="51"
height="100"
<rect
x="49"
width="51"
height="100"
/>
</mask>
<g class="c-cr__cam-fov"
:style="cameraPanStyle"
<g
class="c-cr__cam-fov"
:style="cameraPanStyle"
>
<g mask="url(#mask2)">
<rect class="c-cr__cam-fov-r"
x="49"
width="51"
height="100"
:style="cameraFOVStyleRightHalf"
<rect
class="c-cr__cam-fov-r"
x="49"
width="51"
height="100"
:style="cameraFOVStyleRightHalf"
/>
</g>
<g mask="url(#mask1)">
<rect class="c-cr__cam-fov-l"
width="51"
height="100"
:style="cameraFOVStyleLeftHalf"
<rect
class="c-cr__cam-fov-l"
width="51"
height="100"
:style="cameraFOVStyleLeftHalf"
/>
</g>
</g>
</g>
<!-- Spacecraft body -->
<path v-if="hasHeading"
class="c-cr__spacecraft-body"
fill-rule="evenodd"
clip-rule="evenodd"
d="M37 49C35.3431 49 34 50.3431 34 52V82C34 83.6569 35.3431 85 37 85H63C64.6569 85 66 83.6569 66 82V52C66 50.3431 64.6569 49 63 49H37ZM50 52L58 60H55V67H45V60H42L50 52Z"
:style="headingStyle"
<path
v-if="hasHeading"
class="c-cr__spacecraft-body"
fill-rule="evenodd"
clip-rule="evenodd"
d="M37 49C35.3431 49 34 50.3431 34 52V82C34 83.6569 35.3431 85 37 85H63C64.6569 85 66 83.6569 66 82V52C66 50.3431 64.6569 49 63 49H37ZM50 52L58 60H55V67H45V60H42L50 52Z"
:style="headingStyle"
/>
<!-- NSEW and ticks -->
<g class="c-cr__nsew"
:style="compassRoseStyle"
<g
class="c-cr__nsew"
:style="compassRoseStyle"
>
<g class="c-cr__ticks-major">
<path d="M50 3L43 10H57L50 3Z" />
<path d="M4 51V49H10V51H4Z"
class="--hide-min"
<path
d="M4 51V49H10V51H4Z"
class="--hide-min"
/>
<path d="M49 96V90H51V96H49Z"
class="--hide-min"
<path
d="M49 96V90H51V96H49Z"
class="--hide-min"
/>
<path d="M90 49V51H96V49H90Z"
class="--hide-min"
<path
d="M90 49V51H96V49H90Z"
class="--hide-min"
/>
</g>
<g class="c-cr__ticks-minor --hide-small">
@@ -148,53 +168,63 @@
<path d="M51 10L49 10V4L51 4V10Z" />
</g>
<g class="c-cr__nsew-text">
<path :style="cardinalTextRotateW"
class="c-cr__nsew-w --hide-small"
d="M56.7418 45.004H54.1378L52.7238 52.312H52.6958L51.2258 45.004H48.7758L47.3058 52.312H47.2778L45.8638 45.004H43.2598L45.9618 55H48.6078L49.9798 48.112H50.0078L51.3798 55H53.9838L56.7418 45.004Z"
<path
:style="cardinalTextRotateW"
class="c-cr__nsew-w --hide-small"
d="M56.7418 45.004H54.1378L52.7238 52.312H52.6958L51.2258 45.004H48.7758L47.3058 52.312H47.2778L45.8638 45.004H43.2598L45.9618 55H48.6078L49.9798 48.112H50.0078L51.3798 55H53.9838L56.7418 45.004Z"
/>
<path :style="cardinalTextRotateE"
class="c-cr__nsew-e --hide-small"
d="M46.104 55H54.21V52.76H48.708V50.856H53.608V48.84H48.708V47.09H54.07V45.004H46.104V55Z"
<path
:style="cardinalTextRotateE"
class="c-cr__nsew-e --hide-small"
d="M46.104 55H54.21V52.76H48.708V50.856H53.608V48.84H48.708V47.09H54.07V45.004H46.104V55Z"
/>
<path :style="cardinalTextRotateS"
class="c-cr__nsew-s --hide-small"
d="M45.6531 51.64C45.6671 54.202 47.6971 55.21 49.9931 55.21C52.1911 55.21 54.3471 54.398 54.3471 51.864C54.3471 50.058 52.8911 49.386 51.4491 48.98C49.9931 48.574 48.5511 48.434 48.5511 47.664C48.5511 47.006 49.2511 46.81 49.8111 46.81C50.6091 46.81 51.4631 47.104 51.4211 48.014H54.0251C54.0111 45.76 52.0091 44.794 50.0211 44.794C48.1451 44.794 45.9471 45.648 45.9471 47.832C45.9471 49.666 47.4451 50.31 48.8731 50.716C50.3151 51.122 51.7431 51.29 51.7431 52.172C51.7431 52.914 50.9311 53.194 50.1471 53.194C49.0411 53.194 48.3131 52.816 48.2571 51.64H45.6531Z"
<path
:style="cardinalTextRotateS"
class="c-cr__nsew-s --hide-small"
d="M45.6531 51.64C45.6671 54.202 47.6971 55.21 49.9931 55.21C52.1911 55.21 54.3471 54.398 54.3471 51.864C54.3471 50.058 52.8911 49.386 51.4491 48.98C49.9931 48.574 48.5511 48.434 48.5511 47.664C48.5511 47.006 49.2511 46.81 49.8111 46.81C50.6091 46.81 51.4631 47.104 51.4211 48.014H54.0251C54.0111 45.76 52.0091 44.794 50.0211 44.794C48.1451 44.794 45.9471 45.648 45.9471 47.832C45.9471 49.666 47.4451 50.31 48.8731 50.716C50.3151 51.122 51.7431 51.29 51.7431 52.172C51.7431 52.914 50.9311 53.194 50.1471 53.194C49.0411 53.194 48.3131 52.816 48.2571 51.64H45.6531Z"
/>
<path :style="cardinalTextRotateN"
class="c-cr__nsew-n"
d="M42.5935 60H46.7935V49.32H46.8415L52.7935 60H57.3775V42.864H53.1775V53.424H53.1295L47.1775 42.864H42.5935V60Z"
<path
:style="cardinalTextRotateN"
class="c-cr__nsew-n"
d="M42.5935 60H46.7935V49.32H46.8415L52.7935 60H57.3775V42.864H53.1775V53.424H53.1295L47.1775 42.864H42.5935V60Z"
/>
</g>
</g>
</g>
<defs>
<radialGradient id="paint0_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 50) rotate(90) scale(50)"
<radialGradient
id="paint0_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 50) rotate(90) scale(50)"
>
<stop offset="0.751387"
stop-opacity="0"
<stop
offset="0.751387"
stop-opacity="0"
/>
<stop offset="1"
stop-color="white"
<stop
offset="1"
stop-color="white"
/>
</radialGradient>
<radialGradient id="paint1_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 -7) rotate(-90) scale(18.5)"
<radialGradient
id="paint1_radial"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(50 -7) rotate(-90) scale(18.5)"
>
<stop offset="0.716377"
stop-color="#FFCC00"
<stop
offset="0.716377"
stop-color="#FFCC00"
/>
<stop offset="1"
stop-color="#FF9900"
stop-opacity="0"
<stop
offset="1"
stop-color="#FF9900"
stop-opacity="0"
/>
</radialGradient>
</defs>

View File

@@ -21,11 +21,13 @@
-->
<template>
<div ref="imagery"
class="c-imagery-tsv c-timeline-holder"
<div
ref="imagery"
class="c-imagery-tsv c-timeline-holder"
>
<div ref="imageryHolder"
class="c-imagery-tsv__contents u-contents"
<div
ref="imageryHolder"
class="c-imagery-tsv__contents u-contents"
>
</div>
</div>

View File

@@ -30,50 +30,57 @@
>
<div class="c-imagery__main-image-wrapper has-local-controls">
<div class="h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover c-image-controls__controls">
<span class="c-image-controls__sliders"
draggable="true"
@dragstart="startDrag"
<span
class="c-image-controls__sliders"
draggable="true"
@dragstart="startDrag"
>
<div class="c-image-controls__slider-wrapper icon-brightness">
<input v-model="filters.brightness"
type="range"
min="0"
max="500"
<input
v-model="filters.brightness"
type="range"
min="0"
max="500"
>
</div>
<div class="c-image-controls__slider-wrapper icon-contrast">
<input v-model="filters.contrast"
type="range"
min="0"
max="500"
<input
v-model="filters.contrast"
type="range"
min="0"
max="500"
>
</div>
</span>
<span class="t-reset-btn-holder c-imagery__lc__reset-btn c-image-controls__btn-reset">
<a class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
<a
class="s-icon-button icon-reset t-btn-reset"
@click="filters={brightness: 100, contrast: 100}"
></a>
</span>
</div>
<div ref="imageBG"
class="c-imagery__main-image__bg"
:class="{'paused unnsynced': isPaused && !isFixed,'stale':false }"
@click="expand"
<div
ref="imageBG"
class="c-imagery__main-image__bg"
:class="{'paused unnsynced': isPaused && !isFixed,'stale':false }"
@click="expand"
>
<div class="image-wrapper"
:style="{
'width': `${sizedImageDimensions.width}px`,
'height': `${sizedImageDimensions.height}px`
}"
<div
class="image-wrapper"
:style="{
'width': `${sizedImageDimensions.width}px`,
'height': `${sizedImageDimensions.height}px`
}"
>
<img ref="focusedImage"
class="c-imagery__main-image__image js-imageryView-image"
:src="imageUrl"
:style="{
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
}"
:data-openmct-image-timestamp="time"
:data-openmct-object-keystring="keyString"
<img
ref="focusedImage"
class="c-imagery__main-image__image js-imageryView-image"
:src="imageUrl"
:style="{
'filter': `brightness(${filters.brightness}%) contrast(${filters.contrast}%)`
}"
:data-openmct-image-timestamp="time"
:data-openmct-object-keystring="keyString"
>
<Compass
v-if="shouldDisplayCompass"
@@ -85,16 +92,18 @@
</div>
</div>
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
title="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
<button
class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
title="Previous image"
:disabled="isPrevDisabled"
@click="prevImage()"
></button>
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
title="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
<button
class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
title="Next image"
:disabled="isNextDisabled"
@click="nextImage()"
></button>
<div class="c-imagery__control-bar">
@@ -130,29 +139,33 @@
</div>
</div>
</div>
<div class="c-imagery__thumbs-wrapper"
:class="[
{ 'is-paused': isPaused && !isFixed },
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
]"
<div
class="c-imagery__thumbs-wrapper"
:class="[
{ 'is-paused': isPaused && !isFixed },
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
]"
>
<div
ref="thumbsWrapper"
class="c-imagery__thumbs-scroll-area"
@scroll="handleScroll"
>
<div v-for="(image, index) in imageHistory"
:key="image.url + image.time"
class="c-imagery__thumb c-thumb"
:class="{ selected: focusedImageIndex === index && isPaused }"
@click="setFocusedImage(index, thumbnailClick)"
<div
v-for="(image, index) in imageHistory"
:key="image.url + image.time"
class="c-imagery__thumb c-thumb"
:class="{ selected: focusedImageIndex === index && isPaused }"
@click="setFocusedImage(index, thumbnailClick)"
>
<a href=""
:download="image.imageDownloadName"
@click.prevent
<a
href=""
:download="image.imageDownloadName"
@click.prevent
>
<img class="c-thumb__image"
:src="image.url"
<img
class="c-thumb__image"
:src="image.url"
>
</a>
<div class="c-thumb__timestamp">{{ image.formattedTime }}</div>

View File

@@ -30,6 +30,7 @@ export default {
this.timeSystemChange = this.timeSystemChange.bind(this);
this.setDataTimeContext = this.setDataTimeContext.bind(this);
this.setDataTimeContext();
this.openmct.objectViews.on('clearData', this.clearData);
// set
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
@@ -54,6 +55,7 @@ export default {
}
this.stopFollowingDataTimeContext();
this.openmct.objectViews.off('clearData', this.clearData);
},
methods: {
setDataTimeContext() {
@@ -151,6 +153,25 @@ export default {
this.imageHistory = imagery;
}
},
clearData(domainObjectToClear) {
// global clearData button is accepted therefore no truthy check on inputted param
const clearDataForObjectSelected = Boolean(domainObjectToClear);
if (clearDataForObjectSelected) {
const idsEqual = this.openmct.objects.areIdsEqual(
domainObjectToClear.identifier,
this.domainObject.identifier
);
if (!idsEqual) {
return;
}
}
// splice array to encourage garbage collection
this.imageHistory.splice(0, this.imageHistory.length);
// requesting history effectively clears imageHistory array
return this.requestHistory();
},
timeSystemChange() {
this.timeSystem = this.timeContext.timeSystem();
this.timeKey = this.timeSystem.key;

View File

@@ -27,6 +27,7 @@ import {
resetApplicationState,
simulateKeyEvent
} from 'utils/testing';
import ClearDataPlugin from '../clearData/plugin';
const ONE_MINUTE = 1000 * 60;
const TEN_MINUTES = ONE_MINUTE * 10;
@@ -83,6 +84,7 @@ describe("The Imagery View Layouts", () => {
let telemetryPromise;
let telemetryPromiseResolve;
let cleanupFirst;
let isClearDataTriggered;
let openmct;
let parent;
@@ -201,6 +203,10 @@ describe("The Imagery View Layouts", () => {
});
spyOn(openmct.telemetry, 'request').and.callFake(() => {
if (isClearDataTriggered) {
return [];
}
telemetryPromiseResolve(imageTelemetry);
return telemetryPromise;
@@ -323,6 +329,8 @@ describe("The Imagery View Layouts", () => {
let applicableViews;
let imageryViewProvider;
let imageryView;
let clearDataPlugin;
let clearDataAction;
beforeEach(() => {
@@ -330,16 +338,21 @@ describe("The Imagery View Layouts", () => {
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
imageryView.show(child);
clearDataPlugin = new ClearDataPlugin(
['example.imagery'],
{indicator: true}
);
openmct.install(clearDataPlugin);
clearDataAction = openmct.actions.getAction('clear-data-action');
return Vue.nextTick();
});
// afterEach(() => {
// openmct.time.stopClock();
// openmct.router.removeListener('change:hash', resolveFunction);
//
// imageryView.destroy();
// });
afterEach(() => {
isClearDataTriggered = false;
// openmct.time.stopClock();
// openmct.router.removeListener('change:hash', resolveFunction);
// imageryView.destroy();
});
it("on mount should show the the most recent image", (done) => {
//Looks like we need Vue.nextTick here so that computed properties settle down
@@ -470,6 +483,21 @@ describe("The Imagery View Layouts", () => {
});
});
});
it('clear data action is installed', () => {
expect(clearDataAction).toBeDefined();
});
it('on clearData action should clear data for object is selected', (done) => {
expect(parent.querySelectorAll('.c-imagery__thumb').length).not.toBe(0);
openmct.objectViews.on('clearData', async (_domainObject) => {
await Vue.nextTick();
expect(parent.querySelectorAll('.c-imagery__thumb').length).toBe(0);
done();
});
// stubbed telemetry data will return empty array when true
isClearDataTriggered = true;
clearDataAction.invoke(imageryObject);
});
});
describe("imagery time strip view", () => {

View File

@@ -33,10 +33,7 @@ export default class LinkAction {
}
appliesTo(objectPath) {
let domainObject = objectPath[0];
let type = domainObject && this.openmct.types.get(domainObject.type);
return type && type.definition.creatable;
return true; // link away!
}
invoke(objectPath) {
@@ -77,6 +74,7 @@ export default class LinkAction {
{
name: "location",
control: "locator",
parent: parentDomainObject,
required: true,
validate: this.validate(parentDomainObject),
key: 'location'
@@ -97,6 +95,10 @@ export default class LinkAction {
const parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
const objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
return false;
}
if (!parentCandidateKeystring || !currentParentKeystring) {
return false;
}

View File

@@ -126,6 +126,7 @@ export default class MoveAction {
{
name: "Location",
control: "locator",
parent: parentDomainObject,
required: true,
validate: this.validate(parentDomainObject),
key: 'location'
@@ -144,6 +145,10 @@ export default class MoveAction {
const parentCandidatePath = data.value;
const parentCandidate = parentCandidatePath[0];
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
return false;
}
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
@@ -174,8 +179,9 @@ export default class MoveAction {
let parentType = parent && this.openmct.types.get(parent.type);
let child = objectPath[0];
let childType = child && this.openmct.types.get(child.type);
let isPersistable = this.openmct.objects.isPersistable(child.identifier);
if (child.locked || (parent && parent.locked)) {
if (child.locked || (parent && parent.locked) || !isPersistable) {
return false;
}

View File

@@ -23,46 +23,51 @@
<template>
<div class="c-notebook">
<div class="c-notebook__head">
<Search class="c-notebook__search"
:value="search"
@input="search = $event"
@clear="resetSearch()"
<Search
class="c-notebook__search"
:value="search"
@input="search = $event"
@clear="resetSearch()"
/>
</div>
<SearchResults v-if="search.length"
ref="searchResults"
:domain-object="domainObject"
:results="searchResults"
@changeSectionPage="changeSelectedSection"
@updateEntries="updateEntries"
<SearchResults
v-if="search.length"
ref="searchResults"
:domain-object="domainObject"
:results="searchResults"
@changeSectionPage="changeSelectedSection"
@updateEntries="updateEntries"
/>
<div v-if="!search.length"
class="c-notebook__body"
<div
v-if="!search.length"
class="c-notebook__body"
>
<Sidebar ref="sidebar"
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:default-section-id="defaultSectionId"
:selected-section-id="getSelectedSectionId()"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
@defaultPageDeleted="cleanupDefaultNotebook"
@defaultSectionDeleted="cleanupDefaultNotebook"
@pagesChanged="pagesChanged"
@selectPage="selectPage"
@sectionsChanged="sectionsChanged"
@selectSection="selectSection"
@toggleNav="toggleNav"
<Sidebar
ref="sidebar"
class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
:class="[{'is-expanded': showNav}, {'c-drawer--push': !sidebarCoversEntries}, {'c-drawer--overlays': sidebarCoversEntries}]"
:default-page-id="defaultPageId"
:selected-page-id="getSelectedPageId()"
:default-section-id="defaultSectionId"
:selected-section-id="getSelectedSectionId()"
:domain-object="domainObject"
:page-title="domainObject.configuration.pageTitle"
:section-title="domainObject.configuration.sectionTitle"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
@defaultPageDeleted="cleanupDefaultNotebook"
@defaultSectionDeleted="cleanupDefaultNotebook"
@pagesChanged="pagesChanged"
@selectPage="selectPage"
@sectionsChanged="sectionsChanged"
@selectSection="selectSection"
@toggleNav="toggleNav"
/>
<div class="c-notebook__page-view">
<div class="c-notebook__page-view__header">
<button class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
@click="toggleNav"
<button
class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
@click="toggleNav"
></button>
<div class="c-notebook__page-view__path c-path">
<span class="c-notebook__path__section c-path__item">
@@ -73,59 +78,70 @@
</span>
</div>
<div class="c-notebook__page-view__controls">
<select v-model="showTime"
class="c-notebook__controls__time"
<select
v-model="showTime"
class="c-notebook__controls__time"
>
<option value="0"
:selected="showTime === 0"
<option
value="0"
:selected="showTime === 0"
>
Show all
</option>
<option value="1"
:selected="showTime === 1"
<option
value="1"
:selected="showTime === 1"
>Last hour</option>
<option value="8"
:selected="showTime === 8"
<option
value="8"
:selected="showTime === 8"
>Last 8 hours</option>
<option value="24"
:selected="showTime === 24"
<option
value="24"
:selected="showTime === 24"
>Last 24 hours</option>
</select>
<select v-model="defaultSort"
class="c-notebook__controls__time"
<select
v-model="defaultSort"
class="c-notebook__controls__time"
>
<option value="newest"
:selected="defaultSort === 'newest'"
<option
value="newest"
:selected="defaultSort === 'newest'"
>Newest first</option>
<option value="oldest"
:selected="defaultSort === 'oldest'"
<option
value="oldest"
:selected="defaultSort === 'oldest'"
>Oldest first</option>
</select>
</div>
</div>
<div class="c-notebook__drag-area icon-plus"
@click="newEntry()"
@dragover="dragOver"
@drop.capture="dropCapture"
@drop="dropOnEntry($event)"
<div
class="c-notebook__drag-area icon-plus"
@click="newEntry()"
@dragover="dragOver"
@drop.capture="dropCapture"
@drop="dropOnEntry($event)"
>
<span class="c-notebook__drag-area__label">
To start a new entry, click here or drag and drop any object
</span>
</div>
<div v-if="selectedSection && selectedPage"
ref="notebookEntries"
class="c-notebook__entries"
<div
v-if="selectedSection && selectedPage"
ref="notebookEntries"
class="c-notebook__entries"
>
<NotebookEntry v-for="entry in filteredAndSortedEntries"
:key="entry.id"
:entry="entry"
:domain-object="domainObject"
:selected-page="selectedPage"
:selected-section="selectedSection"
:read-only="false"
@deleteEntry="deleteEntry"
@updateEntry="updateEntry"
<NotebookEntry
v-for="entry in filteredAndSortedEntries"
:key="entry.id"
:entry="entry"
:domain-object="domainObject"
:selected-page="selectedPage"
:selected-section="selectedSection"
:read-only="false"
@deleteEntry="deleteEntry"
@updateEntry="updateEntry"
/>
</div>
</div>
@@ -438,6 +454,8 @@ export default {
const classList = document.querySelector('body').classList;
const isPhone = Array.from(classList).includes('phone');
const isTablet = Array.from(classList).includes('tablet');
// address in https://github.com/nasa/openmct/issues/4875
// eslint-disable-next-line compat/compat
const isPortrait = window.screen.orientation.type.includes('portrait');
const isInLayout = Boolean(this.$el.closest('.c-so-view'));
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
@@ -596,8 +614,9 @@ export default {
this.resetSearch();
const notebookStorage = this.createNotebookStorageObject();
this.updateDefaultNotebook(notebookStorage);
const id = addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed);
this.focusEntryId = id;
addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed).then(id => {
this.focusEntryId = id;
});
},
orientationChange() {
this.formatSidebar();

View File

@@ -1,21 +1,24 @@
<template>
<div class="c-snapshot c-ne__embed">
<div v-if="embed.snapshot"
class="c-ne__embed__snap-thumb"
@click="openSnapshot()"
<div
v-if="embed.snapshot"
class="c-ne__embed__snap-thumb"
@click="openSnapshot()"
>
<img :src="thumbnailImage">
</div>
<div class="c-ne__embed__info">
<div class="c-ne__embed__name">
<a class="c-ne__embed__link"
:class="embed.cssClass"
@click="changeLocation"
<a
class="c-ne__embed__link"
:class="embed.cssClass"
@click="changeLocation"
>{{ embed.name }}</a>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>
<div v-if="embed.snapshot"
class="c-ne__embed__time"
<div
v-if="embed.snapshot"
class="c-ne__embed__time"
>
{{ createdOn }}
</div>

View File

@@ -21,13 +21,17 @@
*****************************************************************************/
<template>
<div class="c-notebook__entry c-ne has-local-controls"
@dragover="changeCursor"
@drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry"
<div
class="c-notebook__entry c-ne has-local-controls"
@dragover="changeCursor"
@drop.capture="cancelEditMode"
@drop.prevent="dropOnEntry"
>
<div class="c-ne__time-and-content">
<div class="c-ne__time">
<template v-if="entry.createdBy">
<span class="c-icon icon-person">{{ entry.createdBy }}</span>
</template>
<span>{{ createdOnDate }}</span>
<span>{{ createdOnTime }}</span>
</div>
@@ -59,27 +63,31 @@
</div>
</template>
<div class="c-snapshots c-ne__embeds">
<NotebookEmbed v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
@removeEmbed="removeEmbed"
@updateEmbed="updateEmbed"
<NotebookEmbed
v-for="embed in entry.embeds"
:key="embed.id"
:embed="embed"
@removeEmbed="removeEmbed"
@updateEmbed="updateEmbed"
/>
</div>
</div>
</div>
<div v-if="!readOnly"
class="c-ne__local-controls--hidden"
<div
v-if="!readOnly"
class="c-ne__local-controls--hidden"
>
<button class="c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
tabindex="-1"
@click="deleteEntry"
<button
class="c-icon-button c-icon-button--major icon-trash"
title="Delete this entry"
tabindex="-1"
@click="deleteEntry"
>
</button>
</div>
<div v-if="readOnly"
class="c-ne__section-and-page"
<div
v-if="readOnly"
class="c-ne__section-and-page"
>
<a
class="c-click-link"
@@ -182,7 +190,7 @@ export default {
this.dropOnEntry = this.dropOnEntry.bind(this);
},
methods: {
addNewEmbed(objectPath) {
async addNewEmbed(objectPath) {
const bounds = this.openmct.time.bounds();
const snapshotMeta = {
bounds,
@@ -190,7 +198,7 @@ export default {
objectPath,
openmct: this.openmct
};
const newEmbed = createNewEmbed(snapshotMeta);
const newEmbed = await createNewEmbed(snapshotMeta);
this.entry.embeds.push(newEmbed);
},
cancelEditMode(event) {
@@ -206,7 +214,7 @@ export default {
deleteEntry() {
this.$emit('deleteEntry', this.entry.id);
},
dropOnEntry($event) {
async dropOnEntry($event) {
event.stopImmediatePropagation();
const snapshotId = $event.dataTransfer.getData('openmct/snapshot/id');
@@ -221,7 +229,7 @@ export default {
} else {
const data = $event.dataTransfer.getData('openmct/domain-object-path');
const objectPath = JSON.parse(data);
this.addNewEmbed(objectPath);
await this.addNewEmbed(objectPath);
}
this.$emit('updateEntry', this.entry);

View File

@@ -8,39 +8,45 @@
<div class="c-object-label__name">
Notebook Snapshots
</div>
<div v-if="snapshots.length"
class="l-browse-bar__object-details"
<div
v-if="snapshots.length"
class="l-browse-bar__object-details"
>{{ snapshots.length }} of {{ getNotebookSnapshotMaxCount() }}
</div>
</div>
<PopupMenu v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems"
<PopupMenu
v-if="snapshots.length > 0"
:popup-menu-items="popupMenuItems"
/>
</div>
</div>
<div class="l-browse-bar__end">
<button class="c-click-icon c-click-icon--major icon-x"
@click="close"
<button
class="c-click-icon c-click-icon--major icon-x"
@click="close"
></button>
</div>
</div><!-- closes l-browse-bar -->
<div class="c-snapshots">
<span v-for="snapshot in snapshots"
:key="snapshot.embedObject.id"
draggable="true"
@dragstart="startEmbedDrag(snapshot, $event)"
<span
v-for="snapshot in snapshots"
:key="snapshot.embedObject.id"
draggable="true"
@dragstart="startEmbedDrag(snapshot, $event)"
>
<NotebookEmbed ref="notebookEmbed"
:key="snapshot.embedObject.id"
:embed="snapshot.embedObject"
:is-snapshot-container="true"
:remove-action-string="'Delete Snapshot'"
@removeEmbed="removeSnapshot"
<NotebookEmbed
ref="notebookEmbed"
:key="snapshot.embedObject.id"
:embed="snapshot.embedObject"
:is-snapshot-container="true"
:remove-action-string="'Delete Snapshot'"
@removeEmbed="removeSnapshot"
/>
</span>
<div v-if="!snapshots.length > 0"
class="hint"
<div
v-if="!snapshots.length > 0"
class="hint"
>
There are no Notebook Snapshots currently.
</div>

View File

@@ -1,11 +1,12 @@
<template>
<div class="c-indicator c-indicator--clickable icon-camera"
:class="[
{ 's-status-off': snapshotCount === 0 },
{ 's-status-on': snapshotCount > 0 },
{ 's-status-caution': snapshotCount === snapshotMaxCount },
{ 'has-new-snapshot': flashIndicator }
]"
<div
class="c-indicator c-indicator--clickable icon-camera"
:class="[
{ 's-status-off': snapshotCount === 0 },
{ 's-status-on': snapshotCount > 0 },
{ 's-status-caution': snapshotCount === snapshotMaxCount },
{ 'has-new-snapshot': flashIndicator }
]"
>
<span class="label c-indicator__label">
{{ indicatorTitle }}

View File

@@ -1,17 +1,19 @@
<template>
<ul class="c-list">
<li v-for="page in pages"
<li
v-for="page in pages"
:key="page.id"
class="c-list__item-h"
>
<Page ref="pageComponent"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:page="page"
:page-title="pageTitle"
@deletePage="deletePage"
@renamePage="updatePage"
@selectPage="selectPage"
<Page
ref="pageComponent"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:page="page"
:page-title="pageTitle"
@deletePage="deletePage"
@renamePage="updatePage"
@selectPage="selectPage"
/>
</li>
</ul>

View File

@@ -1,13 +1,15 @@
<template>
<div class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
:data-id="page.id"
@click="selectPage"
<div
class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultPageId === page.id) }]"
:data-id="page.id"
@click="selectPage"
>
<span class="c-list__item__name js-list__item__name"
:data-id="page.id"
@keydown.enter="updateName"
@blur="updateName"
<span
class="c-list__item__name js-list__item__name"
:data-id="page.id"
@keydown.enter="updateName"
@blur="updateName"
>{{ page.name.length ? page.name : `Unnamed ${pageTitle}` }}</span>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>

View File

@@ -24,16 +24,17 @@
<div class="c-notebook__search-results">
<div class="c-notebook__search-results__header">Search Results ({{ results.length }})</div>
<div class="c-notebook__entries">
<NotebookEntry v-for="(result, index) in results"
:key="index"
:domain-object="domainObject"
:result="result"
:entry="result.entry"
:read-only="true"
:selected-page="result.page"
:selected-section="result.section"
@changeSectionPage="changeSectionPage"
@updateEntries="updateEntries"
<NotebookEntry
v-for="(result, index) in results"
:key="index"
:domain-object="domainObject"
:result="result"
:entry="result.entry"
:read-only="true"
:selected-page="result.page"
:selected-section="result.section"
@changeSectionPage="changeSectionPage"
@updateEntries="updateEntries"
/>
</div>
</div>

View File

@@ -1,17 +1,19 @@
<template>
<ul class="c-list">
<li v-for="section in sections"
<li
v-for="section in sections"
:key="section.id"
class="c-list__item-h"
>
<NotebookSection ref="sectionComponent"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:section="section"
:section-title="sectionTitle"
@deleteSection="deleteSection"
@renameSection="updateSection"
@selectSection="selectSection"
<NotebookSection
ref="sectionComponent"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:section="section"
:section-title="sectionTitle"
@deleteSection="deleteSection"
@renameSection="updateSection"
@selectSection="selectSection"
/>
</li>
</ul>

View File

@@ -1,13 +1,15 @@
<template>
<div class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
:data-id="section.id"
@click="selectSection"
<div
class="c-list__item js-list__item"
:class="[{ 'is-selected': isSelected, 'is-notebook-default' : (defaultSectionId === section.id) }]"
:data-id="section.id"
@click="selectSection"
>
<span class="c-list__item__name js-list__item__name"
:data-id="section.id"
@keydown.enter="updateName"
@blur="updateName"
<span
class="c-list__item__name js-list__item__name"
:data-id="section.id"
@keydown.enter="updateName"
@blur="updateName"
>{{ section.name.length ? section.name : `Unnamed ${sectionTitle}` }}</span>
<PopupMenu :popup-menu-items="popupMenuItems" />
</div>

View File

@@ -7,21 +7,23 @@
</div>
</div>
<div class="c-sidebar__contents-and-controls">
<button class="c-list-button"
@click="addSection"
<button
class="c-list-button"
@click="addSection"
>
<span class="c-button c-list-button__button icon-plus"></span>
<span class="c-list-button__label">Add {{ sectionTitle }}</span>
</button>
<SectionCollection class="c-sidebar__contents"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:domain-object="domainObject"
:sections="sections"
:section-title="sectionTitle"
@defaultSectionDeleted="defaultSectionDeleted"
@updateSection="sectionsChanged"
@selectSection="selectSection"
<SectionCollection
class="c-sidebar__contents"
:default-section-id="defaultSectionId"
:selected-section-id="selectedSectionId"
:domain-object="domainObject"
:sections="sections"
:section-title="sectionTitle"
@defaultSectionDeleted="defaultSectionDeleted"
@updateSection="sectionsChanged"
@selectSection="selectSection"
/>
</div>
</div>
@@ -30,31 +32,34 @@
<div class="c-sidebar__header">
<span class="c-sidebar__header-label">{{ pageTitle }}</span>
</div>
<button class="c-click-icon c-click-icon--major icon-x-in-circle"
@click="toggleNav"
<button
class="c-click-icon c-click-icon--major icon-x-in-circle"
@click="toggleNav"
></button>
</div>
<div class="c-sidebar__contents-and-controls">
<button class="c-list-button"
@click="addPage"
<button
class="c-list-button"
@click="addPage"
>
<span class="c-button c-list-button__button icon-plus"></span>
<span class="c-list-button__label">Add {{ pageTitle }}</span>
</button>
<PageCollection ref="pageCollection"
class="c-sidebar__contents"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:domain-object="domainObject"
:pages="pages"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
:page-title="pageTitle"
@defaultPageDeleted="defaultPageDeleted"
@toggleNav="toggleNav"
@updatePage="pagesChanged"
@selectPage="selectPage"
<PageCollection
ref="pageCollection"
class="c-sidebar__contents"
:default-page-id="defaultPageId"
:selected-page-id="selectedPageId"
:domain-object="domainObject"
:pages="pages"
:sections="sections"
:sidebar-covers-entries="sidebarCoversEntries"
:page-title="pageTitle"
@defaultPageDeleted="defaultPageDeleted"
@toggleNav="toggleNav"
@updatePage="pagesChanged"
@selectPage="selectPage"
/>
</div>
</div>

View File

@@ -41,16 +41,17 @@ export default class Snapshot {
fullSizeImageObjectIdentifier: object.identifier,
thumbnailImage
};
const embed = createNewEmbed(snapshotMeta, snapshot);
if (notebookType === NOTEBOOK_DEFAULT) {
const notebookStorage = getDefaultNotebook();
createNewEmbed(snapshotMeta, snapshot).then(embed => {
if (notebookType === NOTEBOOK_DEFAULT) {
const notebookStorage = getDefaultNotebook();
this._saveToDefaultNoteBook(notebookStorage, embed);
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, notebookStorage.identifier.namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
} else {
this._saveToNotebookSnapshots(object, embed);
}
this._saveToDefaultNoteBook(notebookStorage, embed);
const notebookImageDomainObject = updateNamespaceOfDomainObject(object, notebookStorage.identifier.namespace);
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
} else {
this._saveToNotebookSnapshots(object, embed);
}
});
}
/**
@@ -58,26 +59,26 @@ export default class Snapshot {
*/
_saveToDefaultNoteBook(notebookStorage, embed) {
this.openmct.objects.get(notebookStorage.identifier)
.then(async (domainObject) => {
addNotebookEntry(this.openmct, domainObject, notebookStorage, embed);
.then((domainObject) => {
return addNotebookEntry(this.openmct, domainObject, notebookStorage, embed).then(async () => {
let link = notebookStorage.link;
let link = notebookStorage.link;
// Backwards compatibility fix (old notebook model without link)
if (!link) {
link = await getDefaultNotebookLink(this.openmct, domainObject);
notebookStorage.link = link;
setDefaultNotebook(this.openmct, notebookStorage);
}
// Backwards compatibility fix (old notebook model without link)
if (!link) {
link = await getDefaultNotebookLink(this.openmct, domainObject);
notebookStorage.link = link;
setDefaultNotebook(this.openmct, notebookStorage);
}
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
if (!section || !page) {
return;
}
const { section, page } = getNotebookSectionAndPage(domainObject, notebookStorage.defaultSectionId, notebookStorage.defaultPageId);
if (!section || !page) {
return;
}
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg, link);
const defaultPath = `${domainObject.name} - ${section.name} - ${page.name}`;
const msg = `Saved to Notebook ${defaultPath}`;
this._showNotification(msg, link);
});
});
}

View File

@@ -1,5 +1,17 @@
import objectLink from '../../../ui/mixins/object-link';
async function getUsername(openmct) {
let username = '';
if (openmct.user.hasProvider()) {
const user = await openmct.user.getCurrentUser();
username = user.getName();
}
return username;
}
export const DEFAULT_CLASS = 'notebook-default';
const TIME_BOUNDS = {
START_BOUND: 'tc.startBound',
@@ -61,7 +73,7 @@ export function getHistoricLinkInFixedMode(openmct, bounds, historicLink) {
return params.join('&');
}
export function createNewEmbed(snapshotMeta, snapshot = '') {
export async function createNewEmbed(snapshotMeta, snapshot = '') {
const {
bounds,
link,
@@ -83,10 +95,12 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
});
const name = domainObject.name;
const type = domainObject.identifier.key;
const createdBy = await getUsername(openmct);
return {
bounds,
createdOn: date,
createdBy,
cssClass,
domainObject,
historicLink,
@@ -97,7 +111,7 @@ export function createNewEmbed(snapshotMeta, snapshot = '') {
};
}
export function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
export async function addNotebookEntry(openmct, domainObject, notebookStorage, embed = null, entryText = '') {
if (!openmct || !domainObject || !notebookStorage) {
return;
}
@@ -109,10 +123,12 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
? [embed]
: [];
const createdBy = await getUsername(openmct);
const id = `entry-${date}`;
const entry = {
id,
createdOn: date,
createdBy,
text: entryText,
embeds
};

View File

@@ -127,7 +127,7 @@ describe('Notebook Entries:', () => {
expect(entries.length).toEqual(0);
});
it('addNotebookEntry adds entry', () => {
it('addNotebookEntry adds entry', async () => {
const unlisten = openmct.objects.observe(notebookDomainObject, '*', (object) => {
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
@@ -135,17 +135,38 @@ describe('Notebook Entries:', () => {
unlisten();
});
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
});
it('getEntryPosById returns valid position', () => {
const entryId1 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
it('addNotebookEntry adds active user to entry', async () => {
const USER = 'Timmy';
openmct.user.hasProvider = () => true;
openmct.user.getCurrentUser = () => {
return Promise.resolve({
getName: () => {
return USER;
}
});
};
const unlisten = openmct.objects.observe(notebookDomainObject, '*', (object) => {
const entries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);
expect(entries[0].createdBy).toEqual(USER);
unlisten();
});
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
});
it('getEntryPosById returns valid position', async () => {
const entryId1 = await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position1 = NotebookEntries.getEntryPosById(entryId1, notebookDomainObject, selectedSection, selectedPage);
const entryId2 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const entryId2 = await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position2 = NotebookEntries.getEntryPosById(entryId2, notebookDomainObject, selectedSection, selectedPage);
const entryId3 = NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const entryId3 = await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
const position3 = NotebookEntries.getEntryPosById(entryId3, notebookDomainObject, selectedSection, selectedPage);
const success = position1 === 0
@@ -155,9 +176,9 @@ describe('Notebook Entries:', () => {
expect(success).toBe(true);
});
it('deleteNotebookEntries deletes correct page entries', () => {
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
it('deleteNotebookEntries deletes correct page entries', async () => {
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
await NotebookEntries.addNotebookEntry(openmct, notebookDomainObject, notebookStorage);
NotebookEntries.deleteNotebookEntries(openmct, notebookDomainObject, selectedSection, selectedPage);
const afterEntries = NotebookEntries.getNotebookEntries(notebookDomainObject, selectedSection, selectedPage);

View File

@@ -21,8 +21,9 @@
-->
<template>
<div ref="plan"
class="c-plan c-timeline-holder"
<div
ref="plan"
class="c-plan c-timeline-holder"
>
<template v-if="viewBounds && !options.compact">
<swim-lane>
@@ -36,8 +37,9 @@
/>
</swim-lane>
</template>
<div ref="planHolder"
class="c-plan__contents u-contents"
<div
ref="planHolder"
class="c-plan__contents u-contents"
>
</div>
</div>

View File

@@ -21,10 +21,11 @@
-->
<template>
<div class="c-inspector__properties c-inspect-properties">
<plan-activity-view v-for="activity in activities"
:key="activity.id"
:activity="activity"
:heading="heading"
<plan-activity-view
v-for="activity in activities"
:key="activity.id"
:activity="activity"
:heading="heading"
/>
</div>
</template>

View File

@@ -21,18 +21,21 @@
-->
<template>
<div v-if="timeProperties.length"
class="u-contents"
<div
v-if="timeProperties.length"
class="u-contents"
>
<div class="c-inspect-properties__header">
{{ heading }}
</div>
<ul v-for="timeProperty in timeProperties"
<ul
v-for="timeProperty in timeProperties"
:key="timeProperty.id"
class="c-inspect-properties__section"
>
<activity-property :label="timeProperty.label"
:value="timeProperty.value"
<activity-property
:label="timeProperty.label"
:value="timeProperty.value"
/>
</ul>
</div>

View File

@@ -21,6 +21,7 @@
*****************************************************************************/
export function getValidatedPlan(domainObject) {
let sourceMap = domainObject.sourceMap;
let body = domainObject.selectFile.body;
let json = {};
if (typeof body === 'string') {
@@ -33,5 +34,33 @@ export function getValidatedPlan(domainObject) {
json = body;
}
return json;
if (sourceMap !== undefined && sourceMap.activities !== undefined && sourceMap.groupId !== undefined) {
let mappedJson = {};
json[sourceMap.activities].forEach((activity) => {
if (activity[sourceMap.groupId]) {
const groupIdKey = activity[sourceMap.groupId];
let groupActivity = {
...activity
};
if (sourceMap.start) {
groupActivity.start = activity[sourceMap.start];
}
if (sourceMap.end) {
groupActivity.end = activity[sourceMap.end];
}
if (!mappedJson[groupIdKey]) {
mappedJson[groupIdKey] = [];
}
mappedJson[groupIdKey].push(groupActivity);
}
});
return mappedJson;
} else {
return json;
}
}

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