Compare commits
	
		
			2 Commits
		
	
	
		
			v2.0.3
			...
			table-memo
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 41c9731f4c | ||
|   | ed74673df6 | 
| @@ -2,7 +2,7 @@ version: 2.1 | ||||
| executors: | ||||
|   pw-focal-development: | ||||
|     docker: | ||||
|       - image: mcr.microsoft.com/playwright:v1.19.2-focal | ||||
|       - image: mcr.microsoft.com/playwright:v1.19.1-focal | ||||
|     environment: | ||||
|       NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed | ||||
| parameters: | ||||
| @@ -101,7 +101,7 @@ jobs: | ||||
|             equal: [ "FirefoxESR", <<parameters.browser>> ] | ||||
|           steps: | ||||
|             - browser-tools/install-firefox: | ||||
|                 version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/           | ||||
|                 version: "91.4.0esr" #https://archive.mozilla.org/pub/firefox/releases/           | ||||
|       - when: | ||||
|           condition: | ||||
|             equal: [ "FirefoxHeadless", <<parameters.browser>> ] | ||||
| @@ -113,7 +113,7 @@ jobs: | ||||
|           steps: | ||||
|             - browser-tools/install-chrome: | ||||
|                 replace-existing: false | ||||
|       - run: npm run test -- --browsers=<<parameters.browser>> | ||||
|       - run: npm run test:coverage -- --browsers=<<parameters.browser>> | ||||
|       - save_cache_cmd: | ||||
|           node-version: <<parameters.node-version>> | ||||
|       - store_test_results: | ||||
| @@ -142,22 +142,21 @@ workflows: | ||||
|   overall-circleci-commit-status: #These jobs run on every commit | ||||
|     jobs: | ||||
|       - lint: | ||||
|           name: node16-lint | ||||
|           node-version: lts/gallium | ||||
|       - unit-test: | ||||
|           name: node12-chrome | ||||
|           node-version: lts/erbium | ||||
|           browser: ChromeHeadless | ||||
|       - unit-test:  | ||||
|           name: node14-chrome | ||||
|           node-version: lts/fermium | ||||
|           browser: ChromeHeadless | ||||
|           post-steps: | ||||
|             - upload_code_covio | ||||
|             - upload_code_covio   | ||||
|       - unit-test: | ||||
|           name: node16-chrome | ||||
|           node-version: lts/gallium | ||||
|           browser: ChromeHeadless | ||||
|       - unit-test: | ||||
|           name: node18-chrome | ||||
|           node-version: "18" | ||||
|           browser: ChromeHeadless | ||||
|           browser: ChromeHeadless              | ||||
|       - e2e-test: | ||||
|           name: e2e-ci | ||||
|           node-version: lts/gallium | ||||
| @@ -165,9 +164,13 @@ workflows: | ||||
|   the-nightly: #These jobs do not run on PRs, but against master at night | ||||
|     jobs: | ||||
|       - unit-test: | ||||
|           name: node16-firefoxESR-nightly | ||||
|           node-version: lts/gallium | ||||
|           name: node12-firefoxESR-nightly | ||||
|           node-version: lts/erbium | ||||
|           browser: FirefoxESR | ||||
|       - unit-test: | ||||
|           name: node12-chrome-nightly | ||||
|           node-version: lts/erbium | ||||
|           browser: ChromeHeadless | ||||
|       - unit-test: | ||||
|           name: node14-firefox-nightly | ||||
|           node-version: lts/fermium | ||||
| @@ -180,10 +183,6 @@ workflows: | ||||
|           name: node16-chrome-nightly | ||||
|           node-version: lts/gallium | ||||
|           browser: ChromeHeadless | ||||
|       - unit-test: | ||||
|           name: node18-chrome | ||||
|           node-version: "18" | ||||
|           browser: ChromeHeadless | ||||
|       - npm-audit: | ||||
|           node-version: lts/gallium | ||||
|       - e2e-test: | ||||
|   | ||||
							
								
								
									
										12
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @@ -11,14 +11,12 @@ 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", | ||||
|         "requireConfigFile": false, | ||||
|         "parser": "babel-eslint", | ||||
|         "allowImportExportEverywhere": true, | ||||
|         "ecmaVersion": 2015, | ||||
|         "ecmaFeatures": { | ||||
| @@ -37,6 +35,7 @@ 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", | ||||
| @@ -240,12 +239,13 @@ module.exports = { | ||||
|         ], | ||||
|         "vue/max-attributes-per-line": ["error", { | ||||
|             "singleline": 1, | ||||
|             "multiline": 1, | ||||
|             "multiline": { | ||||
|                 "max": 1, | ||||
|                 "allowFirstLine": true | ||||
|             } | ||||
|         }], | ||||
|         "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" | ||||
|  | ||||
|     }, | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,8 +11,6 @@ updates: | ||||
|       - "dependencies" | ||||
|       - "pr:e2e" | ||||
|       - "pr:daveit" | ||||
|       - "pr:visual" | ||||
|       - "pr:platform" | ||||
|  | ||||
|   - package-ecosystem: "github-actions" | ||||
|     directory: "/" | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							| @@ -28,7 +28,7 @@ jobs: | ||||
|  | ||||
|     steps: | ||||
|     - name: Checkout repository | ||||
|       uses: actions/checkout@v3 | ||||
|       uses: actions/checkout@v2 | ||||
|  | ||||
|     # Initializes the CodeQL tools for scanning. | ||||
|     - name: Initialize CodeQL | ||||
|   | ||||
							
								
								
									
										15
									
								
								.github/workflows/e2e-pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/e2e-pr.yml
									
									
									
									
										vendored
									
									
								
							| @@ -9,12 +9,7 @@ on: | ||||
| jobs: | ||||
|   e2e-full: | ||||
|     if: ${{ github.event.label.name == 'pr:e2e' }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: | ||||
|           - ubuntu-latest | ||||
|           - windows-latest | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Trigger Success | ||||
|         uses: actions/github-script@v6 | ||||
| @@ -26,15 +21,15 @@ jobs: | ||||
|               repo: "openmct", | ||||
|               body: 'Started e2e Run. Follow along: https://github.com/nasa/openmct/actions/runs/' + context.runId | ||||
|             }) | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: actions/setup-node@v3 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - run: npx playwright@1.19.2 install | ||||
|       - run: npx playwright install-deps | ||||
|       - run: npm install | ||||
|       - run: npm run test:e2e:full | ||||
|       - name: Archive test results | ||||
|         uses: actions/upload-artifact@v3 | ||||
|         uses: actions/upload-artifact@v2 | ||||
|         with: | ||||
|           path: test-results | ||||
|       - name: Test success | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/e2e-visual.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/e2e-visual.yml
									
									
									
									
										vendored
									
									
								
							| @@ -13,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@v3 | ||||
|       - uses: actions/setup-node@v3 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - run: npx playwright@1.19.2 install | ||||
|       - run: npx playwright install-deps | ||||
|       - run: npm install | ||||
|       - name: Run the e2e visual tests | ||||
|         run: npm run test:e2e:visual | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,10 +10,10 @@ jobs: | ||||
|   e2e: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           ref: ${{ github.event.inputs.version }} | ||||
|       - uses: actions/setup-node@v3 | ||||
|       - uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - run: npm install | ||||
|   | ||||
							
								
								
									
										90
									
								
								.github/workflows/lighthouse.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										90
									
								
								.github/workflows/lighthouse.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,94 +5,16 @@ on: | ||||
|       version: | ||||
|         description: 'Which branch do you want to test?' # Limited to branch for now | ||||
|         required: false | ||||
|         default: 'master' | ||||
|   pull_request: | ||||
|     types:  | ||||
|       - labeled | ||||
|         default: 'master'  | ||||
| jobs: | ||||
|   lighthouse-pr: | ||||
|     if: ${{ github.event.label.name == 'pr:lighthouse' }} | ||||
|   lighthouse: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout Master for Baseline | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           ref: master #explicitly checkout master for baseline | ||||
|       - name: Install Node 16 | ||||
|         uses: actions/setup-node@v3 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - 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 16 | ||||
|         uses: actions/setup-node@v3 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - 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 | ||||
|       - uses: actions/checkout@v2 | ||||
|         with: | ||||
|           ref: ${{ github.event.inputs.version }} | ||||
|       - name: Install Node 14 | ||||
|         uses: actions/setup-node@v3 | ||||
|       - uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: '16' | ||||
|       - name: Cache node modules | ||||
|         uses: actions/cache@v3 | ||||
|         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 | ||||
|          | ||||
|       - run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps | ||||
|       - run: lhci autorun | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/npm-prerelease.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/npm-prerelease.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,8 +11,8 @@ jobs: | ||||
|   build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: actions/setup-node@v3 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: 16 | ||||
|       - run: npm install | ||||
| @@ -22,8 +22,8 @@ jobs: | ||||
|     needs: build | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v3 | ||||
|       - uses: actions/setup-node@v3 | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: 16 | ||||
|           registry-url: https://registry.npmjs.org/ | ||||
|   | ||||
							
								
								
									
										34
									
								
								.github/workflows/pr-platform.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/pr-platform.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,34 +0,0 @@ | ||||
| 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: | ||||
|           - 14 | ||||
|           - 16 | ||||
|           - 18 | ||||
|         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 | ||||
							
								
								
									
										61
									
								
								.npmignore
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								.npmignore
									
									
									
									
									
								
							| @@ -1,27 +1,44 @@ | ||||
| # Ignore everything first (will not ignore special files like LICENSE.md, | ||||
| # README.md, and package.json)... | ||||
| /**/* | ||||
| *.scssc | ||||
| *.zip | ||||
| *.gzip | ||||
| *.tgz | ||||
| *.DS_Store | ||||
|  | ||||
| # ...but include these folders... | ||||
| !/dist/**/* | ||||
| !/src/**/* | ||||
| *.sass-cache | ||||
| *COMPILE.css | ||||
|  | ||||
| # We might be able to remove this if it is not imported by any project directly. | ||||
| # https://github.com/nasa/openmct/issues/4992 | ||||
| !/example/**/* | ||||
| # Intellij project configuration files | ||||
| *.idea | ||||
| *.iml | ||||
|  | ||||
| # We will remove this in https://github.com/nasa/openmct/issues/4922 | ||||
| !/app.js | ||||
| # External dependencies | ||||
|  | ||||
| # ...except for these files in the above folders. | ||||
| /src/**/*Spec.js | ||||
| /src/**/test/ | ||||
| # TODO move test utils into test/ folders | ||||
| /src/utils/testing.js | ||||
| # Build output | ||||
| target | ||||
|  | ||||
| # Also include these special top-level files. | ||||
| !copyright-notice.js | ||||
| !copyright-notice.html | ||||
| !index.html | ||||
| !openmct.js | ||||
| !SECURITY.md | ||||
| # Mac OS X Finder | ||||
| .DS_Store | ||||
|  | ||||
| # Closed source libraries | ||||
| closed-lib | ||||
|  | ||||
| # Node, Bower dependencies | ||||
| node_modules | ||||
| bower_components | ||||
|  | ||||
| Procfile | ||||
|  | ||||
| # Protractor logs | ||||
| protractor/logs | ||||
|  | ||||
| # npm-debug log | ||||
| npm-debug.log | ||||
|  | ||||
| # Infra and tests | ||||
| .circleci | ||||
| .github | ||||
| e2e | ||||
| codecov.yml | ||||
| lighthouserc.yml | ||||
| *.Spec.js | ||||
| karma.conf.js | ||||
|   | ||||
							
								
								
									
										5
									
								
								.npmrc
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								.npmrc
									
									
									
									
									
								
							| @@ -1,4 +1,9 @@ | ||||
| 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 | ||||
| @@ -65,12 +65,6 @@ 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  | ||||
|   | ||||
							
								
								
									
										2
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								app.js
									
									
									
									
									
								
							| @@ -64,7 +64,7 @@ app.use(require('webpack-dev-middleware')( | ||||
|     compiler, | ||||
|     { | ||||
|         publicPath: '/dist', | ||||
|         stats: 'errors-warnings' | ||||
|         logLevel: 'warn' | ||||
|     } | ||||
| )); | ||||
|  | ||||
|   | ||||
| @@ -1,9 +0,0 @@ | ||||
| // 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; | ||||
| @@ -1,62 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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(); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,62 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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" | ||||
|     }); | ||||
| }); | ||||
| @@ -1,166 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Verify that where we click on canvas shows the number we clicked on | ||||
|         // Note that any number will do, we just care that a number exists | ||||
|         await expect(page.locator('.value-to-display-nearestValue')).toContainText(/[+-]?([0-9]*[.])?[0-9]+/); | ||||
|  | ||||
|     }); | ||||
| }); | ||||
| @@ -1,27 +0,0 @@ | ||||
| (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: [] | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| }()); | ||||
| @@ -1,77 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 | ||||
|     }); | ||||
| }); | ||||
| @@ -1,48 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 | ||||
|     }); | ||||
| }); | ||||
| @@ -1,46 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 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 | ||||
|     }); | ||||
| }); | ||||
| @@ -1,66 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 Clock. | ||||
| */ | ||||
|  | ||||
| const { test, expect } = require('@playwright/test'); | ||||
|  | ||||
| test.describe('Clock Generator', () => { | ||||
|  | ||||
|     test('Timezone dropdown will collapse when clicked outside or on dropdown icon again', async ({ page }) => { | ||||
|         test.info().annotations.push({ | ||||
|             type: 'issue', | ||||
|             description: 'https://github.com/nasa/openmct/issues/4878' | ||||
|         }); | ||||
|         //Go to baseURL | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         //Click the Create button | ||||
|         await page.click('button:has-text("Create")'); | ||||
|  | ||||
|         // Click Clock | ||||
|         await page.click('text=Clock'); | ||||
|  | ||||
|         // Click .icon-arrow-down | ||||
|         await page.locator('.icon-arrow-down').click(); | ||||
|         //verify if the autocomplete dropdown is visible | ||||
|         await expect(page.locator(".optionPreSelected")).toBeVisible(); | ||||
|         // Click .icon-arrow-down | ||||
|         await page.locator('.icon-arrow-down').click(); | ||||
|  | ||||
|         // Verify clicking on the autocomplete arrow collapses the dropdown | ||||
|         await expect(page.locator(".optionPreSelected")).not.toBeVisible(); | ||||
|  | ||||
|         // Click timezone input to open dropdown | ||||
|         await page.locator('.autocompleteInput').click(); | ||||
|         //verify if the autocomplete dropdown is visible | ||||
|         await expect(page.locator(".optionPreSelected")).toBeVisible(); | ||||
|  | ||||
|         // Verify clicking outside the autocomplete dropdown collapses it | ||||
|         await page.locator('text=Timezone').click(); | ||||
|         // Verify clicking on the autocomplete arrow collapses the dropdown | ||||
|         await expect(page.locator(".optionPreSelected")).not.toBeVisible(); | ||||
|  | ||||
|     }); | ||||
| }); | ||||
| @@ -21,163 +21,28 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /* | ||||
| This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. Note: this | ||||
| suite is sharing state between tests which is considered an anti-pattern. Implimenting in this way to | ||||
| demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites. | ||||
| This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. | ||||
| */ | ||||
|  | ||||
| const { test, expect } = require('@playwright/test'); | ||||
|  | ||||
| let conditionSetUrl; | ||||
| let getConditionSetIdentifierFromUrl; | ||||
|  | ||||
| test('Create new Condition Set object and store @localStorage', async ({ page, context }) => { | ||||
|     //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 text=OK | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(), | ||||
|         page.click('text=OK') | ||||
|     ]); | ||||
|  | ||||
|     await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); | ||||
|     //Save localStorage for future test execution | ||||
|     await context.storageState({ path: './e2e/tests/recycled_storage.json' }); | ||||
|  | ||||
|     //Set object identifier from url | ||||
|     conditionSetUrl = await page.url(); | ||||
|     console.log('conditionSetUrl ' + conditionSetUrl); | ||||
|  | ||||
|     getConditionSetIdentifierFromUrl = await conditionSetUrl.split('/').pop().split('?')[0]; | ||||
|     console.log('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl); | ||||
|  | ||||
| }); | ||||
|  | ||||
| test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { | ||||
|     //Load localStorage for subsequent tests | ||||
|     test.use({ storageState: './e2e/tests/recycled_storage.json' }); | ||||
|  | ||||
|     //Begin suite of tests again localStorage | ||||
|     test('Condition set object properties persist in main view and inspector', async ({ page }) => { | ||||
|         //Navigate to baseURL with injected localStorage | ||||
|         await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         //Assertions on loaded Condition Set in main view | ||||
|         await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); | ||||
|  | ||||
|         //Assertions on loaded Condition Set in Inspector | ||||
|         await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy; | ||||
|  | ||||
|         //Reload Page | ||||
|         await Promise.all([ | ||||
|             page.reload(), | ||||
|             page.waitForLoadState('networkidle') | ||||
|         ]); | ||||
|  | ||||
|         //Re-verify after reload | ||||
|         await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); | ||||
|         //Assertions on loaded Condition Set in Inspector | ||||
|         await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy; | ||||
|  | ||||
|     }); | ||||
|     test('condition set object can be modified on @localStorage', async ({ page }) => { | ||||
|         await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         //Assertions on loaded Condition Set in main view | ||||
|         await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); | ||||
|  | ||||
|         //Update the Condition Set properties | ||||
|         // Click Edit Button | ||||
|         await page.locator('text=Conditions View Snapshot >> button').nth(3).click(); | ||||
|  | ||||
|         //Edit Condition Set Name from main view | ||||
|         await page.locator('text=Unnamed Condition Set').first().fill('Renamed Condition Set'); | ||||
|         await page.locator('text=Renamed Condition Set').first().press('Enter'); | ||||
|         // Click Save Button | ||||
|         await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|         // Click Save and Finish Editing Option | ||||
|         await page.locator('text=Save and Finish Editing').click(); | ||||
|  | ||||
|         //Verify Main section reflects updated Name Property | ||||
|         await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); | ||||
|  | ||||
|         // Verify Inspector properties | ||||
|         // Verify Inspector has updated Name property | ||||
|         await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); | ||||
|         // Verify Inspector Details has updated Name property | ||||
|         await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); | ||||
|  | ||||
|         // Verify Tree reflects updated Name proprety | ||||
|         // Expand Tree | ||||
|         await page.locator('text=Open MCT My Items >> span >> nth=3').click(); | ||||
|         // Verify Condition Set Object is renamed in Tree | ||||
|         await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); | ||||
|         // Verify Search Tree reflects renamed Name property | ||||
|         await page.locator('input[type="search"]').fill('Renamed'); | ||||
|         await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); | ||||
|  | ||||
|         //Reload Page | ||||
|         await Promise.all([ | ||||
|             page.reload(), | ||||
|             page.waitForLoadState('networkidle') | ||||
|         ]); | ||||
|  | ||||
|         //Verify Main section reflects updated Name Property | ||||
|         await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); | ||||
|  | ||||
|         // Verify Inspector properties | ||||
|         // Verify Inspector has updated Name property | ||||
|         await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); | ||||
|         // Verify Inspector Details has updated Name property | ||||
|         await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); | ||||
|  | ||||
|         // Verify Tree reflects updated Name proprety | ||||
|         // Expand Tree | ||||
|         await page.locator('text=Open MCT My Items >> span >> nth=3').click(); | ||||
|         // Verify Condition Set Object is renamed in Tree | ||||
|         await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); | ||||
|         // Verify Search Tree reflects renamed Name property | ||||
|         await page.locator('input[type="search"]').fill('Renamed'); | ||||
|         await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); | ||||
|     }); | ||||
|     test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => { | ||||
|         //Navigate to baseURL | ||||
| test.describe('condition set', () => { | ||||
|     test('create new button `condition set` creates new condition object', async ({ page }) => { | ||||
|         //Go to baseURL | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         //Expect Unnamed Condition Set to be visible in Main View | ||||
|         await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set")')).toBeVisible(); | ||||
|         //Click the Create button | ||||
|         await page.click('button:has-text("Create")'); | ||||
|  | ||||
|         // Search for Unnamed Condition Set | ||||
|         await page.locator('input[type="search"]').fill('Unnamed Condition Set'); | ||||
|         // Right Click to Open Actions Menu | ||||
|         await page.locator('a:has-text("Unnamed Condition Set")').click({ | ||||
|             button: 'right' | ||||
|         }); | ||||
|         // Click Remove Action | ||||
|         await page.locator('text=Remove').click(); | ||||
|         // Click text=Condition Set | ||||
|         await page.click('text=Condition Set'); | ||||
|  | ||||
|         await page.locator('text=OK').click(); | ||||
|         // Click text=OK | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/), | ||||
|             page.click('text=OK') | ||||
|         ]); | ||||
|  | ||||
|         //Expect Unnamed Condition Set to be removed in Main View | ||||
|         await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set")')).not.toBeVisible(); | ||||
|  | ||||
|         await page.locator('.c-search__clear-input').click(); | ||||
|         // Search for Unnamed Condition Set | ||||
|         await page.locator('input[type="search"]').fill('Unnamed Condition Set'); | ||||
|         // Expect Unnamed Condition Set to be removed | ||||
|         await expect(page.locator('a:has-text("Unnamed Condition Set")')).not.toBeVisible(); | ||||
|  | ||||
|         //Feature? | ||||
|         //Domain Object is still available by direct URL after delete | ||||
|         await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); | ||||
|         await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); | ||||
|  | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -1,217 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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 imagery, | ||||
| but only assume that example imagery is present. | ||||
| */ | ||||
|  | ||||
| const { test, expect } = require('@playwright/test'); | ||||
|  | ||||
| test.describe('Example Imagery', () => { | ||||
|  | ||||
|     test.beforeEach(async ({ page }) => { | ||||
|         page.on('console', msg => console.log(msg.text())) | ||||
|         //Go to baseURL | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         //Click the Create button | ||||
|         await page.click('button:has-text("Create")'); | ||||
|  | ||||
|         // Click text=Example Imagery | ||||
|         await page.click('text=Example Imagery'); | ||||
|  | ||||
|         // Click text=OK | ||||
|         await Promise.all([ | ||||
|             page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/), | ||||
|             page.click('text=OK') | ||||
|         ]); | ||||
|  | ||||
|         await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery'); | ||||
|     }); | ||||
|  | ||||
|     const backgroundImageSelector = '.c-imagery__main-image__background-image'; | ||||
|     test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => { | ||||
|         const bgImageLocator = await page.locator(backgroundImageSelector); | ||||
|         const deltaYStep = 100; //equivalent to 1x zoom | ||||
|         await bgImageLocator.hover(); | ||||
|         const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox(); | ||||
|         // zoom in | ||||
|         await bgImageLocator.hover(); | ||||
|         await page.mouse.wheel(0, deltaYStep * 2); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|         const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox(); | ||||
|         // zoom out | ||||
|         await bgImageLocator.hover(); | ||||
|         await page.mouse.wheel(0, -deltaYStep); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|         const imageMouseZoomedOut = await page.locator(backgroundImageSelector).boundingBox(); | ||||
|  | ||||
|         expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height); | ||||
|         expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width); | ||||
|         expect(imageMouseZoomedOut.height).toBeLessThan(imageMouseZoomedIn.height); | ||||
|         expect(imageMouseZoomedOut.width).toBeLessThan(imageMouseZoomedIn.width); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     test('Can use alt+drag to move around image once zoomed in', async ({ page }) => { | ||||
|         const deltaYStep = 100; //equivalent to 1x zoom | ||||
|  | ||||
|         const bgImageLocator = await page.locator(backgroundImageSelector); | ||||
|         await bgImageLocator.hover(); | ||||
|         // zoom in | ||||
|         await page.mouse.wheel(0, deltaYStep * 2); | ||||
|         await bgImageLocator.hover(); | ||||
|         const zoomedBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2; | ||||
|         const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2; | ||||
|         // move to the right | ||||
|  | ||||
|         // center the mouse pointer | ||||
|         await page.mouse.move(imageCenterX, imageCenterY); | ||||
|  | ||||
|         // pan right | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX - 200, imageCenterY, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         const afterRightPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(zoomedBoundingBox.x).toBeGreaterThan(afterRightPanBoundingBox.x); | ||||
|  | ||||
|         // pan left | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX, imageCenterY, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         const afterLeftPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(afterRightPanBoundingBox.x).toBeLessThan(afterLeftPanBoundingBox.x); | ||||
|  | ||||
|         // pan up | ||||
|         await page.mouse.move(imageCenterX, imageCenterY); | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX, imageCenterY + 200, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         const afterUpPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(afterUpPanBoundingBox.y).toBeGreaterThan(afterLeftPanBoundingBox.y); | ||||
|  | ||||
|         // pan down | ||||
|         await page.keyboard.down('Alt'); | ||||
|         await page.mouse.down(); | ||||
|         await page.mouse.move(imageCenterX, imageCenterY - 200, 10); | ||||
|         await page.mouse.up(); | ||||
|         await page.keyboard.up('Alt'); | ||||
|         const afterDownPanBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(afterDownPanBoundingBox.y).toBeLessThan(afterUpPanBoundingBox.y); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     test('Can use + - buttons to zoom on the image', async ({ page }) => { | ||||
|         const bgImageLocator = await page.locator(backgroundImageSelector); | ||||
|         await bgImageLocator.hover(); | ||||
|         const zoomInBtn = await page.locator('.t-btn-zoom-in'); | ||||
|         const zoomOutBtn = await page.locator('.t-btn-zoom-out'); | ||||
|         const initialBoundingBox = await bgImageLocator.boundingBox(); | ||||
|  | ||||
|         await zoomInBtn.click(); | ||||
|         await zoomInBtn.click(); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|         const zoomedInBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); | ||||
|         expect(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width); | ||||
|  | ||||
|         await zoomOutBtn.click(); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|         const zoomedOutBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect(zoomedOutBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height); | ||||
|         expect(zoomedOutBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width); | ||||
|  | ||||
|     }); | ||||
|  | ||||
|     test('Can use the reset button to reset the image', async ({ page }) => { | ||||
|         const bgImageLocator = await page.locator(backgroundImageSelector); | ||||
|         await bgImageLocator.hover(); | ||||
|         const zoomInBtn = await page.locator('.t-btn-zoom-in'); | ||||
|         const zoomResetBtn = await page.locator('.t-btn-zoom-reset'); | ||||
|         const initialBoundingBox = await bgImageLocator.boundingBox(); | ||||
|  | ||||
|         await zoomInBtn.click(); | ||||
|         await zoomInBtn.click(); | ||||
|         // wait for zoom animation to finish | ||||
|         await bgImageLocator.hover(); | ||||
|         const zoomedInBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height); | ||||
|         expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width); | ||||
|  | ||||
|         await zoomResetBtn.click(); | ||||
|         await bgImageLocator.hover(); | ||||
|  | ||||
|         const resetBoundingBox = await bgImageLocator.boundingBox(); | ||||
|         expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height); | ||||
|         expect.soft(resetBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width); | ||||
|  | ||||
|         expect.soft(resetBoundingBox.height).toEqual(initialBoundingBox.height); | ||||
|         expect(resetBoundingBox.width).toEqual(initialBoundingBox.width); | ||||
|     }); | ||||
|  | ||||
|     //test('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     //test('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     //test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); | ||||
|     //test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     //test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     //test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|  | ||||
| test.describe('Example Imagery in Display layout', () => { | ||||
|     test.skip('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.skip('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|  | ||||
| test.describe('Example Imagery in Flexible layout', () => { | ||||
|     test.skip('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.skip('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
|  | ||||
| test.describe('Example Imagery in Tabs view', () => { | ||||
|     test.skip('Can use Mouse Wheel to zoom in and out of previous image'); | ||||
|     test.skip('Can use alt+drag to move around image once zoomed in'); | ||||
|     test.skip('Can zoom into the latest image and the real-time/fixed-time imagery will pause'); | ||||
|     test.skip('Can zoom into a previous image from thumbstrip in real-time or fixed-time'); | ||||
|     test.skip('Clicking on the left arrow should pause the imagery and go to previous image'); | ||||
|     test.skip('If the imagery view is in pause mode, it should not be updated when new images come in'); | ||||
|     test.skip('If the imagery view is not in pause mode, it should be updated when new images come in'); | ||||
| }); | ||||
| @@ -1,190 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /* | ||||
| Test for plot autoscale. | ||||
| */ | ||||
|  | ||||
| const { test: _test, expect } = require('@playwright/test'); | ||||
|  | ||||
| // create a new `test` API that will not append platform details to snapshot | ||||
| // file names, only for the tests in this file, so that the same snapshots will | ||||
| // be used for all platforms. | ||||
| const test = _test.extend({ | ||||
|     _autoSnapshotSuffix: [ | ||||
|         async ({}, use, testInfo) => { | ||||
|             testInfo.snapshotSuffix = ''; | ||||
|             await use(); | ||||
|         }, | ||||
|         { auto: true } | ||||
|     ] | ||||
| }); | ||||
|  | ||||
| test.use({ | ||||
|     viewport: { | ||||
|         width: 1280, | ||||
|         height: 720 | ||||
|     } | ||||
| }); | ||||
|  | ||||
| test.describe('ExportAsJSON', () => { | ||||
|     test('autoscale off causes no error from undefined user range', async ({ page }) => { | ||||
|         await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|         await setTimeRange(page); | ||||
|  | ||||
|         await createSinewaveOverlayPlot(page); | ||||
|  | ||||
|         await testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']); | ||||
|  | ||||
|         await turnOffAutoscale(page); | ||||
|  | ||||
|         const canvas = page.locator('canvas').nth(1); | ||||
|  | ||||
|         // Make sure that after turning off autoscale, the user selected range values start at the same values the plot had prior. | ||||
|         await Promise.all([ | ||||
|             testYTicks(page, ['-1.00', '-0.50', '0.00', '0.50', '1.00']), | ||||
|             new Promise(r => setTimeout(r, 100)) | ||||
|                 .then(() => canvas.screenshot()) | ||||
|                 .then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-prepan.png', { maxDiffPixels: 40 })) | ||||
|         ]); | ||||
|  | ||||
|         let errorCount = 0; | ||||
|  | ||||
|         function onError() { | ||||
|             errorCount++; | ||||
|         } | ||||
|  | ||||
|         page.on('pageerror', onError); | ||||
|  | ||||
|         await page.keyboard.down('Alt'); | ||||
|  | ||||
|         await canvas.dragTo(canvas, { | ||||
|             sourcePosition: { | ||||
|                 x: 200, | ||||
|                 y: 200 | ||||
|             }, | ||||
|             targetPosition: { | ||||
|                 x: 400, | ||||
|                 y: 400 | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         await page.keyboard.up('Alt'); | ||||
|  | ||||
|         page.off('pageerror', onError); | ||||
|  | ||||
|         // There would have been an error at this point. So if there isn't, then | ||||
|         // we fixed it. | ||||
|         expect(errorCount).toBe(0); | ||||
|  | ||||
|         // Ensure the drag worked. | ||||
|         await Promise.all([ | ||||
|             testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00']), | ||||
|             new Promise(r => setTimeout(r, 100)) | ||||
|                 .then(() => canvas.screenshot()) | ||||
|                 .then(shot => expect(shot).toMatchSnapshot('autoscale-canvas-panned.png', { maxDiffPixels: 40 })) | ||||
|         ]); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  * @param {string} start | ||||
|  * @param {string} end | ||||
|  */ | ||||
| async function setTimeRange(page, start = '2022-03-29 22:00:00.000Z', end = '2022-03-29 22:00:30.000Z') { | ||||
|     // Set a specific time range for consistency, otherwise it will change | ||||
|     // on every test to a range based on the current time. | ||||
|  | ||||
|     const timeInputs = page.locator('input.c-input--datetime'); | ||||
|     await timeInputs.first().click(); | ||||
|     await timeInputs.first().fill(start); | ||||
|  | ||||
|     await timeInputs.nth(1).click(); | ||||
|     await timeInputs.nth(1).fill(end); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function createSinewaveOverlayPlot(page) { | ||||
|     // click create button | ||||
|     await page.locator('button:has-text("Create")').click(); | ||||
|  | ||||
|     // add overlay plot with defaults | ||||
|     await page.locator('li:has-text("Overlay Plot")').click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|     ]); | ||||
|  | ||||
|     // save (exit edit mode) | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
|  | ||||
|     // click create button | ||||
|     await page.locator('button:has-text("Create")').click(); | ||||
|  | ||||
|     // add sine wave generator with defaults | ||||
|     await page.locator('li:has-text("Sine Wave Generator")').click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396/5cfa5c69-17bc-4a99-9545-4da8125380c5?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-single' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|     ]); | ||||
|  | ||||
|     // focus the overlay plot | ||||
|     await page.locator('text=Open MCT My Items >> span').nth(3).click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/a9268c6f-45cc-4bcd-a6a0-50ac4036e396?tc.mode=fixed&tc.startBound=1649305424163&tc.endBound=1649307224163&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.locator('text=Unnamed Overlay Plot').first().click() | ||||
|     ]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function turnOffAutoscale(page) { | ||||
|     // enter edit mode | ||||
|     await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click(); | ||||
|  | ||||
|     // uncheck autoscale | ||||
|     await page.locator('text=Y Axis Scaling Auto scale Padding >> input[type="checkbox"]').uncheck(); | ||||
|  | ||||
|     // save | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function testYTicks(page, values) { | ||||
|     const yTicks = page.locator('.gl-plot-y-tick-label'); | ||||
|     let promises = [yTicks.count().then(c => expect(c).toBe(values.length))]; | ||||
|  | ||||
|     for (let i = 0, l = values.length; i < l; i += 1) { | ||||
|         promises.push(expect(yTicks.nth(i)).toHaveText(values[i])); // eslint-disable-line | ||||
|     } | ||||
|  | ||||
|     await Promise.all(promises); | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 24 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 30 KiB | 
| @@ -1,279 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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. | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| /* | ||||
| Tests to verify log plot functionality. | ||||
| */ | ||||
|  | ||||
| const { test, expect } = require('@playwright/test'); | ||||
|  | ||||
| test.describe('Log plot tests', () => { | ||||
|     test.only('Can create a log plot.', async ({ page }) => { | ||||
|         await makeOverlayPlot(page); | ||||
|         await testRegularTicks(page); | ||||
|         await enableEditMode(page); | ||||
|         await enableLogMode(page); | ||||
|         await testLogTicks(page); | ||||
|         await disableLogMode(page); | ||||
|         await testRegularTicks(page); | ||||
|         await enableLogMode(page); | ||||
|         await testLogTicks(page); | ||||
|         await saveOverlayPlot(page); | ||||
|         await testLogTicks(page); | ||||
|         await testLogPlotPixels(page); | ||||
|  | ||||
|         // refresh page | ||||
|         await page.reload(); | ||||
|  | ||||
|         // test log ticks hold up after refresh | ||||
|         await testLogTicks(page); | ||||
|         await testLogPlotPixels(page); | ||||
|     }); | ||||
|  | ||||
|     test.only('Verify that log mode option is reflected in import/export JSON', async ({ page }) => { | ||||
|         await makeOverlayPlot(page); | ||||
|         await enableEditMode(page); | ||||
|         await enableLogMode(page); | ||||
|         await saveOverlayPlot(page); | ||||
|  | ||||
|         // TODO ...export, delete the overlay, then import it... | ||||
|  | ||||
|         await testLogTicks(page); | ||||
|  | ||||
|         // TODO, the plot is slightly at different position that in the other test, so this fails. | ||||
|         // ...We can fix it by copying all steps from the first test... | ||||
|         // await testLogPlotPixels(page); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * Makes an overlay plot with a sine wave generator and clicks on the overlay plot in the sidebar so it is the active thing displayed. | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function makeOverlayPlot(page) { | ||||
|     // fresh page with time range from 2022-03-29 22:00:00.000Z to 2022-03-29 22:00:30.000Z | ||||
|     await page.goto('/', { waitUntil: 'networkidle' }); | ||||
|  | ||||
|     // Set a specific time range for consistency, otherwise it will change | ||||
|     // on every test to a range based on the current time. | ||||
|  | ||||
|     const timeInputs = page.locator('input.c-input--datetime'); | ||||
|     await timeInputs.first().click(); | ||||
|     await timeInputs.first().fill('2022-03-29 22:00:00.000Z'); | ||||
|  | ||||
|     await timeInputs.nth(1).click(); | ||||
|     await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z'); | ||||
|  | ||||
|     // create overlay plot | ||||
|  | ||||
|     await page.locator('button.c-create-button').click(); | ||||
|     await page.locator('li:has-text("Overlay Plot")').click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|     ]); | ||||
|  | ||||
|     // save the overlay plot | ||||
|  | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
|  | ||||
|     // create a sinewave generator | ||||
|  | ||||
|     await page.locator('button.c-create-button').click(); | ||||
|     await page.locator('li:has-text("Sine Wave Generator")').click(); | ||||
|  | ||||
|     // set amplitude to 6, offset 4, period 2 | ||||
|  | ||||
|     await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').click(); | ||||
|     await page.locator('div:nth-child(5) .form-row .c-form-row__controls .form-control .field input').fill('6'); | ||||
|  | ||||
|     await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').click(); | ||||
|     await page.locator('div:nth-child(6) .form-row .c-form-row__controls .form-control .field input').fill('4'); | ||||
|  | ||||
|     await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').click(); | ||||
|     await page.locator('div:nth-child(7) .form-row .c-form-row__controls .form-control .field input').fill('2'); | ||||
|  | ||||
|     // Click OK to make generator | ||||
|  | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f/6e58b26a-8a73-4df6-b3a6-918decc0bbfa?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-single' }*/), | ||||
|         page.locator('text=OK').click() | ||||
|     ]); | ||||
|  | ||||
|     // click on overlay plot | ||||
|  | ||||
|     await page.locator('text=Open MCT My Items >> span').nth(3).click(); | ||||
|     await Promise.all([ | ||||
|         page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/8caf7072-535b-4af6-8394-edd86e3ea35f?tc.mode=fixed&tc.startBound=1648590633191&tc.endBound=1648592433191&tc.timeSystem=utc&view=plot-overlay' }*/), | ||||
|         page.locator('text=Unnamed Overlay Plot').first().click() | ||||
|     ]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function testRegularTicks(page) { | ||||
|     const yTicks = page.locator('.gl-plot-y-tick-label'); | ||||
|     expect(await yTicks.count()).toBe(7); | ||||
|     await expect(yTicks.nth(0)).toHaveText('-2'); | ||||
|     await expect(yTicks.nth(1)).toHaveText('0'); | ||||
|     await expect(yTicks.nth(2)).toHaveText('2'); | ||||
|     await expect(yTicks.nth(3)).toHaveText('4'); | ||||
|     await expect(yTicks.nth(4)).toHaveText('6'); | ||||
|     await expect(yTicks.nth(5)).toHaveText('8'); | ||||
|     await expect(yTicks.nth(6)).toHaveText('10'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function testLogTicks(page) { | ||||
|     const yTicks = page.locator('.gl-plot-y-tick-label'); | ||||
|     expect(await yTicks.count()).toBe(28); | ||||
|     await expect(yTicks.nth(0)).toHaveText('-2.98'); | ||||
|     await expect(yTicks.nth(1)).toHaveText('-2.50'); | ||||
|     await expect(yTicks.nth(2)).toHaveText('-2.00'); | ||||
|     await expect(yTicks.nth(3)).toHaveText('-1.51'); | ||||
|     await expect(yTicks.nth(4)).toHaveText('-1.20'); | ||||
|     await expect(yTicks.nth(5)).toHaveText('-1.00'); | ||||
|     await expect(yTicks.nth(6)).toHaveText('-0.80'); | ||||
|     await expect(yTicks.nth(7)).toHaveText('-0.58'); | ||||
|     await expect(yTicks.nth(8)).toHaveText('-0.40'); | ||||
|     await expect(yTicks.nth(9)).toHaveText('-0.20'); | ||||
|     await expect(yTicks.nth(10)).toHaveText('-0.00'); | ||||
|     await expect(yTicks.nth(11)).toHaveText('0.20'); | ||||
|     await expect(yTicks.nth(12)).toHaveText('0.40'); | ||||
|     await expect(yTicks.nth(13)).toHaveText('0.58'); | ||||
|     await expect(yTicks.nth(14)).toHaveText('0.80'); | ||||
|     await expect(yTicks.nth(15)).toHaveText('1.00'); | ||||
|     await expect(yTicks.nth(16)).toHaveText('1.20'); | ||||
|     await expect(yTicks.nth(17)).toHaveText('1.51'); | ||||
|     await expect(yTicks.nth(18)).toHaveText('2.00'); | ||||
|     await expect(yTicks.nth(19)).toHaveText('2.50'); | ||||
|     await expect(yTicks.nth(20)).toHaveText('2.98'); | ||||
|     await expect(yTicks.nth(21)).toHaveText('3.50'); | ||||
|     await expect(yTicks.nth(22)).toHaveText('4.00'); | ||||
|     await expect(yTicks.nth(23)).toHaveText('4.50'); | ||||
|     await expect(yTicks.nth(24)).toHaveText('5.31'); | ||||
|     await expect(yTicks.nth(25)).toHaveText('7.00'); | ||||
|     await expect(yTicks.nth(26)).toHaveText('8.00'); | ||||
|     await expect(yTicks.nth(27)).toHaveText('9.00'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function enableEditMode(page) { | ||||
|     // turn on edit mode | ||||
|     await page.locator('text=Unnamed Overlay Plot Snapshot >> button').nth(3).click(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function enableLogMode(page) { | ||||
|     // turn on log mode | ||||
|     await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().check(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function disableLogMode(page) { | ||||
|     // turn off log mode | ||||
|     await page.locator('text=Y Axis Label Log mode Auto scale Padding >> input[type="checkbox"]').first().uncheck(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function saveOverlayPlot(page) { | ||||
|     // save overlay plot | ||||
|     await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); | ||||
|     await page.locator('text=Save and Finish Editing').click(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {import('@playwright/test').Page} page | ||||
|  */ | ||||
| async function testLogPlotPixels(page) { | ||||
|     const pixelsMatch = await page.evaluate(async () => { | ||||
|         // TODO get canvas pixels at a few locations to make sure they're the correct color, to test that the plot comes out as expected. | ||||
|  | ||||
|         await new Promise((r) => setTimeout(r, 50)); | ||||
|  | ||||
|         // These are some pixels that should be blue points in the log plot. | ||||
|         // If the plot changes shape to an unexpected shape, this will | ||||
|         // likely fail, which is what we want. | ||||
|         // | ||||
|         // I found these pixels by pausing playwright in debug mode at this | ||||
|         // point, and using similar code as below to output the pixel data, then | ||||
|         // I logged those pixels here. | ||||
|         const expectedBluePixels = [ | ||||
|             // TODO these pixel sets only work with the first test, but not the second test. | ||||
|  | ||||
|             // [60, 35], | ||||
|             // [121, 125], | ||||
|             // [156, 377], | ||||
|             // [264, 73], | ||||
|             // [372, 186], | ||||
|             // [576, 73], | ||||
|             // [659, 439], | ||||
|             // [675, 423] | ||||
|  | ||||
|             [60, 35], | ||||
|             [120, 125], | ||||
|             [156, 375], | ||||
|             [264, 73], | ||||
|             [372, 185], | ||||
|             [575, 72], | ||||
|             [659, 437], | ||||
|             [675, 421] | ||||
|         ]; | ||||
|  | ||||
|         // The first canvas in the DOM is the one that has the plot point | ||||
|         // icons (canvas 2d), which is the one we are testing. The second | ||||
|         // one in the DOM is the WebGL canvas with the line. (Why aren't | ||||
|         // they both WebGL?) | ||||
|         const canvas = document.querySelector('canvas'); | ||||
|  | ||||
|         const ctx = canvas.getContext('2d'); | ||||
|  | ||||
|         for (const pixel of expectedBluePixels) { | ||||
|             // XXX Possible optimization: call getImageData only once with | ||||
|             // area including all pixels to be tested. | ||||
|             const data = ctx.getImageData(pixel[0], pixel[1], 1, 1).data; | ||||
|  | ||||
|             // #43b0ffff <-- openmct cyanish-blue with 100% opacity | ||||
|             // if (data[0] !== 0x43 || data[1] !== 0xb0 || data[2] !== 0xff || data[3] !== 0xff) { | ||||
|             if (data[0] === 0 && data[1] === 0 && data[2] === 0 && data[3] === 0) { | ||||
|                 // If any pixel is empty, it means we didn't hit a plot point. | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     }); | ||||
|  | ||||
|     expect(pixelsMatch).toBe(true); | ||||
| } | ||||
| @@ -1,69 +0,0 @@ | ||||
| /***************************************************************************** | ||||
|  * 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(); | ||||
|     }); | ||||
| }); | ||||
| @@ -1,22 +0,0 @@ | ||||
| { | ||||
|   "cookies": [], | ||||
|   "origins": [ | ||||
|     { | ||||
|       "origin": "http://localhost:8080", | ||||
|       "localStorage": [ | ||||
|         { | ||||
|           "name": "tcHistory", | ||||
|           "value": "{\"utc\":[{\"start\":1651513945533,\"end\":1651515745533}]}" | ||||
|         }, | ||||
|         { | ||||
|           "name": "mct", | ||||
|           "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1651515746374,\"modified\":1651515746374},\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"e35a066b-eb0e-4b05-a4c9-cc31dc202572\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1651515746373,\"location\":\"mine\",\"persisted\":1651515746373}}" | ||||
|         }, | ||||
|         { | ||||
|           "name": "mct-tree-expanded", | ||||
|           "value": "[]" | ||||
|         } | ||||
|       ] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| @@ -22,14 +22,14 @@ | ||||
|  | ||||
| /* | ||||
| Collection of Visual Tests set to run in a default context. The tests within this suite | ||||
| are only meant to run against openmct's app.js started by `npm run start` within the | ||||
| are only meant to run against openmct's app.js started by `npm run start` within the  | ||||
| `./e2e/playwright-visual.config.js` file. | ||||
|  | ||||
| These should only use functional expect statements to verify assumptions about the state | ||||
| These should only use functional expect statements to verify assumptions about the state  | ||||
| in a test and not for functional verification of correctness. Visual tests are not supposed | ||||
| to "fail" on assertions. Instead, they should be used to detect changes between builds or branches. | ||||
|  | ||||
| Note: Larger testsuite sizes are OK due to the setup time associated with these tests. | ||||
| Note: Larger testsuite sizes are OK due to the setup time associated with these tests.  | ||||
| */ | ||||
|  | ||||
| const { test, expect } = require('@playwright/test'); | ||||
| @@ -111,63 +111,3 @@ 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'); | ||||
| }); | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class EventTelemetryProvider { | ||||
|  | ||||
|     generateData(firstObservedTime, count, startTime, duration, name) { | ||||
|         const millisecondsSinceStart = startTime - firstObservedTime; | ||||
|         const utc = startTime + (count * duration); | ||||
|         const utc = Math.floor(startTime / duration) * 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 = options.start; | ||||
|         const firstObservedTime = Date.now(); | ||||
|         let count = 0; | ||||
|  | ||||
|         if (options.strategy === 'latest' || options.size === 1) { | ||||
| @@ -83,7 +83,7 @@ class EventTelemetryProvider { | ||||
|         } | ||||
|  | ||||
|         while (start <= end && data.length < size) { | ||||
|             const startTime = options.start + count; | ||||
|             const startTime = Date.now() + count; | ||||
|             data.push(this.generateData(firstObservedTime, count, startTime, duration, domainObject.name)); | ||||
|             start += duration; | ||||
|             count += 1; | ||||
|   | ||||
| @@ -35,7 +35,6 @@ describe('the plugin', () => { | ||||
|         telemetry: { | ||||
|             duration: 0 | ||||
|         }, | ||||
|         options: {}, | ||||
|         type: 'eventGenerator' | ||||
|     }; | ||||
|  | ||||
| @@ -62,13 +61,7 @@ describe('the plugin', () => { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         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 | ||||
|         it("supports requests", async () => { | ||||
|             const telemetry = await openmct.telemetry.request(mockDomainObject); | ||||
|             expect(telemetry[0].message).toContain('CC: Eagle, Houston'); | ||||
|         }); | ||||
|   | ||||
| @@ -26,7 +26,7 @@ import { | ||||
| } from '../../src/utils/testing'; | ||||
| import ExampleUserProvider from './ExampleUserProvider'; | ||||
|  | ||||
| xdescribe("The Example User Plugin", () => { | ||||
| describe("The Example User Plugin", () => { | ||||
|     let openmct; | ||||
|  | ||||
|     beforeEach(() => { | ||||
|   | ||||
| @@ -35,8 +35,8 @@ define([ | ||||
|         phase: 0 | ||||
|     }; | ||||
|  | ||||
|     function GeneratorProvider(openmct) { | ||||
|         this.workerInterface = new WorkerInterface(openmct); | ||||
|     function GeneratorProvider() { | ||||
|         this.workerInterface = new WorkerInterface(); | ||||
|     } | ||||
|  | ||||
|     GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) { | ||||
|   | ||||
| @@ -21,13 +21,20 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| define([ | ||||
|     'raw-loader!./generatorWorker.js', | ||||
|     'uuid' | ||||
| ], function ( | ||||
|     workerText, | ||||
|     uuid | ||||
| ) { | ||||
|     function WorkerInterface(openmct) { | ||||
|         // eslint-disable-next-line no-undef | ||||
|         const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`; | ||||
|  | ||||
|     var workerBlob = new Blob( | ||||
|         [workerText], | ||||
|         {type: 'application/javascript'} | ||||
|     ); | ||||
|     var workerUrl = URL.createObjectURL(workerBlob); | ||||
|  | ||||
|     function WorkerInterface() { | ||||
|         this.worker = new Worker(workerUrl); | ||||
|         this.worker.onmessage = this.onMessage.bind(this); | ||||
|         this.callbacks = {}; | ||||
|   | ||||
| @@ -121,7 +121,7 @@ | ||||
|  | ||||
|         var data = []; | ||||
|  | ||||
|         for (; nextStep < end && data.length < 5000; nextStep += step) { | ||||
|         for (; nextStep < end; nextStep += step) { | ||||
|             data.push({ | ||||
|                 utc: nextStep, | ||||
|                 yesterday: nextStep - 60 * 60 * 24 * 1000, | ||||
|   | ||||
| @@ -146,7 +146,7 @@ define([ | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         openmct.telemetry.addProvider(new GeneratorProvider(openmct)); | ||||
|         openmct.telemetry.addProvider(new GeneratorProvider()); | ||||
|         openmct.telemetry.addProvider(new GeneratorMetadataProvider()); | ||||
|         openmct.telemetry.addProvider(new SinewaveLimitProvider()); | ||||
|     }; | ||||
|   | ||||
| @@ -77,7 +77,7 @@ | ||||
|  | ||||
|  | ||||
|         openmct.install(openmct.plugins.LocalStorage()); | ||||
|  | ||||
|        | ||||
|         openmct.install(openmct.plugins.example.Generator()); | ||||
|         openmct.install(openmct.plugins.example.EventGeneratorPlugin()); | ||||
|         openmct.install(openmct.plugins.example.ExampleImagery()); | ||||
| @@ -190,12 +190,11 @@ | ||||
|         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', 'example.imagery'], | ||||
|             ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'], | ||||
|             {indicator: true} | ||||
|         )); | ||||
|         openmct.install(openmct.plugins.Clock({ enableClockIndicator: true })); | ||||
|         openmct.install(openmct.plugins.Timer()); | ||||
|         openmct.install(openmct.plugins.Timelist()); | ||||
|         openmct.start(); | ||||
|     </script> | ||||
| </html> | ||||
|   | ||||
| @@ -1,2 +1,3 @@ | ||||
| const testsContext = require.context('.', true, /^\.\/(src|example)\/.*Spec.js$/); | ||||
| const testsContext = require.context('.', true, /\/(src|platform|\.\/example)\/.*Spec.js$/); | ||||
|  | ||||
| testsContext.keys().forEach(testsContext); | ||||
|   | ||||
| @@ -22,9 +22,29 @@ | ||||
|  | ||||
| /*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.coverage.js'); | ||||
|     const webpackConfig = require('./webpack.dev.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: '', | ||||
| @@ -38,15 +58,11 @@ module.exports = (config) => { | ||||
|             { | ||||
|                 pattern: 'dist/inMemorySearchWorker.js*', | ||||
|                 included: false | ||||
|             }, | ||||
|             { | ||||
|                 pattern: 'dist/generatorWorker.js*', | ||||
|                 included: false | ||||
|             } | ||||
|         ], | ||||
|         port: 9876, | ||||
|         reporters: ['spec', 'junit', 'coverage-istanbul'], | ||||
|         browsers: [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'], | ||||
|         reporters: reporters, | ||||
|         browsers: browsers, | ||||
|         client: { | ||||
|             jasmine: { | ||||
|                 random: false, | ||||
| @@ -67,6 +83,12 @@ module.exports = (config) => { | ||||
|         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", | ||||
| @@ -74,7 +96,9 @@ module.exports = (config) => { | ||||
|         }, | ||||
|         coverageIstanbulReporter: { | ||||
|             fixWebpackSourcePaths: true, | ||||
|             dir: "dist/reports/coverage", | ||||
|             dir: process.env.CIRCLE_ARTIFACTS | ||||
|                 ? process.env.CIRCLE_ARTIFACTS + '/coverage' | ||||
|                 : "dist/reports/coverage", | ||||
|             reports: ['lcovonly', 'text-summary'], | ||||
|             thresholds: { | ||||
|                 global: { | ||||
| @@ -96,7 +120,8 @@ module.exports = (config) => { | ||||
|         }, | ||||
|         webpack: webpackConfig, | ||||
|         webpackMiddleware: { | ||||
|             stats: 'errors-warnings' | ||||
|             stats: 'errors-only', | ||||
|             logLevel: 'warn' | ||||
|         }, | ||||
|         concurrency: 1, | ||||
|         singleRun: true, | ||||
|   | ||||
							
								
								
									
										105
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,44 +1,40 @@ | ||||
| { | ||||
|   "name": "openmct", | ||||
|   "version": "2.0.3", | ||||
|   "version": "2.0.1-SNAPSHOT", | ||||
|   "description": "The Open MCT core platform", | ||||
|   "devDependencies": { | ||||
|     "@babel/eslint-parser": "7.16.3", | ||||
|     "@braintree/sanitize-url": "6.0.0", | ||||
|     "@percy/cli": "1.0.4", | ||||
|     "@percy/playwright": "1.0.2", | ||||
|     "@playwright/test": "1.19.2", | ||||
|     "@types/eventemitter3": "^1.0.0", | ||||
|     "@types/jasmine": "^4.0.1", | ||||
|     "@types/karma": "^6.3.2", | ||||
|     "@types/lodash": "^4.14.178", | ||||
|     "@types/mocha": "^9.1.0", | ||||
|     "@braintree/sanitize-url": "5.0.2", | ||||
|     "@percy/cli": "1.0.0-beta.75", | ||||
|     "@percy/playwright": "1.0.1", | ||||
|     "@playwright/test": "1.19.1", | ||||
|     "allure-playwright": "2.0.0-beta.15", | ||||
|     "babel-loader": "8.2.3", | ||||
|     "babel-plugin-istanbul": "6.1.1", | ||||
|     "babel-eslint": "10.1.0", | ||||
|     "comma-separated-values": "3.6.4", | ||||
|     "copy-webpack-plugin": "10.2.0", | ||||
|     "cross-env": "7.0.3", | ||||
|     "core-js": "3.20.3", | ||||
|     "cross-env": "6.0.3", | ||||
|     "css-loader": "4.0.0", | ||||
|     "d3-axis": "1.0.x", | ||||
|     "d3-scale": "1.0.x", | ||||
|     "d3-selection": "1.3.x", | ||||
|     "eslint": "8.13.0", | ||||
|     "eslint-plugin-compat": "4.0.2", | ||||
|     "eslint": "7.0.0", | ||||
|     "eslint-plugin-playwright": "0.8.0", | ||||
|     "eslint-plugin-vue": "8.5.0", | ||||
|     "eslint-plugin-vue": "7.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": "2.0.5", | ||||
|     "git-rev-sync": "3.0.2", | ||||
|     "git-rev-sync": "1.4.0", | ||||
|     "html-loader": "0.5.5", | ||||
|     "html2canvas": "1.4.1", | ||||
|     "imports-loader": "0.8.0", | ||||
|     "jasmine-core": "4.0.1", | ||||
|     "istanbul-instrumenter-loader": "^3.0.1", | ||||
|     "jasmine-core": "4.0.0", | ||||
|     "jsdoc": "3.5.5", | ||||
|     "karma": "6.3.18", | ||||
|     "karma-chrome-launcher": "3.1.1", | ||||
|     "karma": "6.3.15", | ||||
|     "karma-chrome-launcher": "3.1.0", | ||||
|     "karma-cli": "2.0.0", | ||||
|     "karma-coverage": "2.1.1", | ||||
|     "karma-coverage-istanbul-reporter": "3.0.3", | ||||
| @@ -46,55 +42,54 @@ | ||||
|     "karma-jasmine": "4.0.1", | ||||
|     "karma-junit-reporter": "2.0.1", | ||||
|     "karma-sourcemap-loader": "0.3.8", | ||||
|     "karma-spec-reporter": "0.0.34", | ||||
|     "karma-webpack": "5.0.0", | ||||
|     "lighthouse": "9.5.0", | ||||
|     "location-bar": "3.0.1", | ||||
|     "lodash": "4.17.21", | ||||
|     "mini-css-extract-plugin": "2.6.0", | ||||
|     "karma-spec-reporter": "0.0.33", | ||||
|     "karma-webpack": "^5.0.0", | ||||
|     "location-bar": "^3.0.1", | ||||
|     "lodash": "^4.17.12", | ||||
|     "mini-css-extract-plugin": "2.4.5", | ||||
|     "moment": "2.29.1", | ||||
|     "moment-duration-format": "2.3.2", | ||||
|     "moment-timezone": "0.5.34", | ||||
|     "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", | ||||
|     "request": "2.88.2", | ||||
|     "resolve-url-loader": "5.0.0", | ||||
|     "sass": "1.49.9", | ||||
|     "sass-loader": "12.6.0", | ||||
|     "moment-duration-format": "^2.2.2", | ||||
|     "moment-timezone": "0.5.28", | ||||
|     "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.2.1", | ||||
|     "raw-loader": "^0.5.1", | ||||
|     "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", | ||||
|     "uuid": "^3.3.3", | ||||
|     "vue": "2.6.14", | ||||
|     "vue-eslint-parser": "8.3.0", | ||||
|     "vue-eslint-parser": "8.2.0", | ||||
|     "vue-loader": "15.9.8", | ||||
|     "vue-template-compiler": "2.6.14", | ||||
|     "webpack": "5.68.0", | ||||
|     "webpack-cli": "4.9.2", | ||||
|     "webpack-dev-middleware": "5.3.1", | ||||
|     "webpack-hot-middleware": "2.25.1", | ||||
|     "webpack-dev-middleware": "^3.1.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 ./package-lock.json", | ||||
|     "clean": "rm -rf ./dist ./node_modules; rm package-lock.json", | ||||
|     "clean-test-lint": "npm run clean; npm install; npm run test; npm run lint", | ||||
|     "start": "node app.js", | ||||
|     "lint": "eslint example src --ext .js,.vue openmct.js", | ||||
|     "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: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:debug": "npm run test:e2e:local -- --debug", | ||||
|     "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: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", | ||||
| @@ -110,18 +105,8 @@ | ||||
|     "url": "https://github.com/nasa/openmct.git" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=14.19.1" | ||||
|     "node": ">=12.22.0" | ||||
|   }, | ||||
|   "overrides": { | ||||
|     "core-js": "3.21.1" | ||||
|   }, | ||||
|   "browserslist": [ | ||||
|     "Firefox ESR", | ||||
|     "not IE 11", | ||||
|     "last 2 Chrome versions", | ||||
|     "unreleased Chrome versions", | ||||
|     "ios_saf > 15" | ||||
|   ], | ||||
|   "author": "", | ||||
|   "license": "Apache-2.0", | ||||
|   "private": true | ||||
|   | ||||
| @@ -241,6 +241,7 @@ define([ | ||||
|         this.branding = BrandingAPI.default; | ||||
|  | ||||
|         // Plugins that are installed by default | ||||
|  | ||||
|         this.install(this.plugins.Plot()); | ||||
|         this.install(this.plugins.Chart()); | ||||
|         this.install(this.plugins.TelemetryTable.default()); | ||||
| @@ -268,6 +269,7 @@ 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()); | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import AutoCompleteField from './components/controls/AutoCompleteField.vue'; | ||||
| import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue'; | ||||
| import CheckBoxField from './components/controls/CheckBoxField.vue'; | ||||
| import Datetime from './components/controls/Datetime.vue'; | ||||
| import FileInput from './components/controls/FileInput.vue'; | ||||
| import Locator from './components/controls/Locator.vue'; | ||||
| @@ -8,13 +7,11 @@ import NumberField from './components/controls/NumberField.vue'; | ||||
| import SelectField from './components/controls/SelectField.vue'; | ||||
| import TextAreaField from './components/controls/TextAreaField.vue'; | ||||
| import TextField from './components/controls/TextField.vue'; | ||||
| import ToggleSwitchField from './components/controls/ToggleSwitchField.vue'; | ||||
|  | ||||
| import Vue from 'vue'; | ||||
|  | ||||
| export const DEFAULT_CONTROLS_MAP = { | ||||
|     'autocomplete': AutoCompleteField, | ||||
|     'checkbox': CheckBoxField, | ||||
|     'composite': ClockDisplayFormatField, | ||||
|     'datetime': Datetime, | ||||
|     'file-input': FileInput, | ||||
| @@ -22,8 +19,7 @@ export const DEFAULT_CONTROLS_MAP = { | ||||
|     'numberfield': NumberField, | ||||
|     'select': SelectField, | ||||
|     'textarea': TextAreaField, | ||||
|     'textfield': TextField, | ||||
|     'toggleSwitch': ToggleSwitchField | ||||
|     'textfield': TextField | ||||
| }; | ||||
|  | ||||
| export default class FormControl { | ||||
| @@ -69,11 +65,10 @@ export default class FormControl { | ||||
|      */ | ||||
|     _getControlViewProvider(control) { | ||||
|         const self = this; | ||||
|         let rowComponent; | ||||
|  | ||||
|         return { | ||||
|             show(element, model, onChange) { | ||||
|                 rowComponent = new Vue({ | ||||
|                 const rowComponent = new Vue({ | ||||
|                     el: element, | ||||
|                     components: { | ||||
|                         FormControlComponent: DEFAULT_CONTROLS_MAP[control] | ||||
| @@ -91,10 +86,8 @@ export default class FormControl { | ||||
|                 }); | ||||
|  | ||||
|                 return rowComponent; | ||||
|             }, | ||||
|             destroy() { | ||||
|                 rowComponent.$destroy(); | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -26,52 +26,45 @@ | ||||
|         <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> | ||||
|   | ||||
| @@ -21,25 +21,21 @@ | ||||
| *****************************************************************************/ | ||||
|  | ||||
| <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> | ||||
| @@ -79,12 +75,10 @@ export default { | ||||
|         rowClass() { | ||||
|             let cssClass = this.cssClass; | ||||
|  | ||||
|             if (!this.row.required) { | ||||
|                 return; | ||||
|             if (this.row.required) { | ||||
|                 cssClass = `${cssClass} req`; | ||||
|             } | ||||
|  | ||||
|             cssClass = `${cssClass} req`; | ||||
|  | ||||
|             if (this.visited && this.valid !== undefined) { | ||||
|                 if (this.valid === true) { | ||||
|                     cssClass = `${cssClass} valid`; | ||||
|   | ||||
| @@ -22,26 +22,20 @@ | ||||
|  | ||||
| <template> | ||||
| <div class="form-control autocomplete"> | ||||
|     <span class="autocompleteInputAndArrow"> | ||||
|         <input | ||||
|             v-model="field" | ||||
|             class="autocompleteInput" | ||||
|             type="text" | ||||
|             @click="inputClicked()" | ||||
|             @keydown="keyDown($event)" | ||||
|         > | ||||
|         <span | ||||
|             class="icon-arrow-down" | ||||
|             @click="arrowClicked()" | ||||
|         ></span> | ||||
|     </span> | ||||
|     <div | ||||
|         class="autocompleteOptions" | ||||
|         @blur="hideOptions = true" | ||||
|     <input v-model="field" | ||||
|            class="autocompleteInput" | ||||
|            type="text" | ||||
|            @click="inputClicked()" | ||||
|            @keydown="keyDown($event)" | ||||
|     > | ||||
|     <span class="icon-arrow-down" | ||||
|           @click="arrowClicked()" | ||||
|     ></span> | ||||
|     <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)" | ||||
| @@ -110,21 +104,10 @@ export default { | ||||
|  | ||||
|                 this.$emit('onChange', data); | ||||
|             } | ||||
|         }, | ||||
|         hideOptions(newValue) { | ||||
|             if (!newValue) { | ||||
|                 // adding a event listener when the hideOpntions is false (dropdown is visible) | ||||
|                 // handleoutsideclick can collapse the dropdown when clicked outside autocomplete | ||||
|                 document.body.addEventListener('click', this.handleOutsideClick); | ||||
|             } else { | ||||
|                 //removing event listener when hideOptions become true (dropdown is collapsed) | ||||
|                 document.body.removeEventListener('click', this.handleOutsideClick); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.options = this.model.options; | ||||
|         this.autocompleteInputAndArrow = this.$el.getElementsByClassName('autocompleteInputAndArrow')[0]; | ||||
|         this.autocompleteInputElement = this.$el.getElementsByClassName('autocompleteInput')[0]; | ||||
|         if (this.options[0].name) { | ||||
|         // If "options" include name, value pair | ||||
| @@ -136,9 +119,6 @@ export default { | ||||
|             this.optionNames = this.options; | ||||
|         } | ||||
|     }, | ||||
|     destroyed() { | ||||
|         document.body.removeEventListener('click', this.handleOutsideClick); | ||||
|     }, | ||||
|     methods: { | ||||
|         decrementOptionIndex() { | ||||
|             if (this.optionIndex === 0) { | ||||
| @@ -192,21 +172,7 @@ export default { | ||||
|             // to show them all the options | ||||
|             this.showFilteredOptions = false; | ||||
|             this.autocompleteInputElement.select(); | ||||
|  | ||||
|             if (this.hideOptions) { | ||||
|                 this.showOptions(); | ||||
|             } else { | ||||
|                 this.hideOptions = true; | ||||
|             } | ||||
|  | ||||
|         }, | ||||
|         handleOutsideClick(event) { | ||||
|             // if click event is detected outside autocomplete (both input & arrow) while the | ||||
|             // dropdown is visible, this will collapse the dropdown. | ||||
|             const clickedInsideAutocomplete = this.autocompleteInputAndArrow.contains(event.target); | ||||
|             if (!clickedInsideAutocomplete && !this.hideOptions) { | ||||
|                 this.hideOptions = true; | ||||
|             } | ||||
|             this.showOptions(); | ||||
|         }, | ||||
|         optionMouseover(optionId) { | ||||
|             this.optionIndex = optionId; | ||||
|   | ||||
| @@ -1,55 +0,0 @@ | ||||
| /***************************************************************************** | ||||
| * 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. | ||||
| *****************************************************************************/ | ||||
|  | ||||
| <template> | ||||
| <span class="form-control shell"> | ||||
|     <span | ||||
|         class="field control" | ||||
|         :class="model.cssClass" | ||||
|     > | ||||
|         <input | ||||
|             type="checkbox" | ||||
|             :checked="isChecked" | ||||
|             @input="toggleCheckBox" | ||||
|         > | ||||
|     </span> | ||||
| </span> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import toggleMixin from '../../toggle-check-box-mixin'; | ||||
|  | ||||
| export default { | ||||
|     mixins: [toggleMixin], | ||||
|     props: { | ||||
|         model: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             isChecked: this.model.value | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
| @@ -22,11 +22,10 @@ | ||||
|  | ||||
| <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> | ||||
|   | ||||
| @@ -22,13 +22,12 @@ | ||||
|  | ||||
| <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> | ||||
|   | ||||
| @@ -22,11 +22,10 @@ | ||||
|  | ||||
| <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 }} | ||||
|   | ||||
| @@ -27,55 +27,50 @@ | ||||
|     <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"> | ||||
|   | ||||
| @@ -22,21 +22,18 @@ | ||||
|  | ||||
| <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> | ||||
|   | ||||
| @@ -22,25 +22,21 @@ | ||||
|  | ||||
| <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" | ||||
|             @input="updateText()" | ||||
|         <input v-model="field" | ||||
|                type="number" | ||||
|                :min="model.min" | ||||
|                :max="model.max" | ||||
|                :step="model.step" | ||||
|                @blur="blur()" | ||||
|         > | ||||
|     </span> | ||||
| </span> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { throttle } from 'lodash'; | ||||
|  | ||||
| export default { | ||||
|     props: { | ||||
|         model: { | ||||
| @@ -53,11 +49,8 @@ export default { | ||||
|             field: this.model.value | ||||
|         }; | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.updateText = throttle(this.updateText.bind(this), 200); | ||||
|     }, | ||||
|     methods: { | ||||
|         updateText() { | ||||
|         blur() { | ||||
|             const data = { | ||||
|                 model: this.model, | ||||
|                 value: this.field | ||||
|   | ||||
| @@ -22,16 +22,14 @@ | ||||
|  | ||||
| <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> | ||||
|   | ||||
| @@ -22,15 +22,13 @@ | ||||
|  | ||||
| <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" | ||||
|             @input="updateText()" | ||||
|         <textarea v-model="field" | ||||
|                   type="text" | ||||
|                   :size="model.size" | ||||
|                   @blur="blur()" | ||||
|         > | ||||
|         </textarea> | ||||
|     </span> | ||||
| @@ -38,8 +36,6 @@ | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { throttle } from 'lodash'; | ||||
|  | ||||
| export default { | ||||
|     props: { | ||||
|         model: { | ||||
| @@ -52,11 +48,8 @@ export default { | ||||
|             field: this.model.value | ||||
|         }; | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.updateText = throttle(this.updateText.bind(this), 500); | ||||
|     }, | ||||
|     methods: { | ||||
|         updateText() { | ||||
|         blur() { | ||||
|             const data = { | ||||
|                 model: this.model, | ||||
|                 value: this.field | ||||
|   | ||||
| @@ -22,23 +22,19 @@ | ||||
|  | ||||
| <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" | ||||
|             @input="updateText()" | ||||
|         <input v-model="field" | ||||
|                type="text" | ||||
|                :size="model.size" | ||||
|                @blur="blur()" | ||||
|         > | ||||
|     </span> | ||||
| </span> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { throttle } from 'lodash'; | ||||
|  | ||||
| export default { | ||||
|     props: { | ||||
|         model: { | ||||
| @@ -51,11 +47,8 @@ export default { | ||||
|             field: this.model.value | ||||
|         }; | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.updateText = throttle(this.updateText.bind(this), 500); | ||||
|     }, | ||||
|     methods: { | ||||
|         updateText() { | ||||
|         blur() { | ||||
|             const data = { | ||||
|                 model: this.model, | ||||
|                 value: this.field | ||||
|   | ||||
| @@ -1,62 +0,0 @@ | ||||
| /***************************************************************************** | ||||
| * 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. | ||||
| *****************************************************************************/ | ||||
|  | ||||
| <template> | ||||
| <span class="form-control shell"> | ||||
|     <span | ||||
|         class="field control" | ||||
|         :class="model.cssClass" | ||||
|     > | ||||
|         <ToggleSwitch | ||||
|             id="switchId" | ||||
|             :checked="isChecked" | ||||
|             @change="toggleCheckBox" | ||||
|         /> | ||||
|     </span> | ||||
| </span> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import toggleMixin from '../../toggle-check-box-mixin'; | ||||
| import ToggleSwitch from '@/ui/components/ToggleSwitch.vue'; | ||||
|  | ||||
| import uuid from 'uuid'; | ||||
|  | ||||
| export default { | ||||
|     components: { | ||||
|         ToggleSwitch | ||||
|     }, | ||||
|     mixins: [toggleMixin], | ||||
|     props: { | ||||
|         model: { | ||||
|             type: Object, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             switchId: `toggleSwitch-${uuid}`, | ||||
|             isChecked: this.model.value | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
| @@ -1,19 +0,0 @@ | ||||
| export default { | ||||
|     data() { | ||||
|         return { | ||||
|             isChecked: false | ||||
|         }; | ||||
|     }, | ||||
|     methods: { | ||||
|         toggleCheckBox(event) { | ||||
|             this.isChecked = !this.isChecked; | ||||
|  | ||||
|             const data = { | ||||
|                 model: this.model, | ||||
|                 value: this.isChecked | ||||
|             }; | ||||
|  | ||||
|             this.$emit('onChange', data); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @@ -1,7 +1,6 @@ | ||||
| <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 | ||||
|   | ||||
| @@ -1,10 +1,8 @@ | ||||
| <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 | ||||
| @@ -36,8 +34,7 @@ | ||||
|         </template> | ||||
|     </ul> | ||||
|  | ||||
|     <ul | ||||
|         v-else | ||||
|     <ul v-else | ||||
|         class="c-super-menu__menu" | ||||
|     > | ||||
|         <li | ||||
|   | ||||
| @@ -36,14 +36,13 @@ class InMemorySearchProvider { | ||||
|          */ | ||||
|         this.MAX_CONCURRENT_REQUESTS = 100; | ||||
|         /** | ||||
|          * If max results is not specified in query, use this as default. | ||||
|          */ | ||||
|         * If max results is not specified in query, use this as default. | ||||
|         */ | ||||
|         this.DEFAULT_MAX_RESULTS = 100; | ||||
|  | ||||
|         this.openmct = openmct; | ||||
|  | ||||
|         this.indexedIds = {}; | ||||
|         this.indexedCompositions = {}; | ||||
|         this.idsToIndex = []; | ||||
|         this.pendingIndex = {}; | ||||
|         this.pendingRequests = 0; | ||||
| @@ -59,6 +58,7 @@ class InMemorySearchProvider { | ||||
|         this.onWorkerMessageError = this.onWorkerMessageError.bind(this); | ||||
|         this.onerror = this.onWorkerError.bind(this); | ||||
|         this.startIndexing = this.startIndexing.bind(this); | ||||
|         this.onMutationOfIndexedObject = this.onMutationOfIndexedObject.bind(this); | ||||
|  | ||||
|         this.openmct.on('start', this.startIndexing); | ||||
|         this.openmct.on('destroy', () => { | ||||
| @@ -68,9 +68,6 @@ class InMemorySearchProvider { | ||||
|                 this.worker.port.onmessageerror = null; | ||||
|                 this.worker.port.close(); | ||||
|             } | ||||
|  | ||||
|             this.destroyObservers(this.indexedIds); | ||||
|             this.destroyObservers(this.indexedCompositions); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @@ -140,7 +137,7 @@ class InMemorySearchProvider { | ||||
|         }; | ||||
|         modelResults.hits = await Promise.all(event.data.results.map(async (hit) => { | ||||
|             const identifier = this.openmct.objects.parseKeyString(hit.keyString); | ||||
|             const domainObject = await this.openmct.objects.get(identifier); | ||||
|             const domainObject = await this.openmct.objects.get(identifier.key); | ||||
|  | ||||
|             return domainObject; | ||||
|         })); | ||||
| @@ -216,52 +213,29 @@ class InMemorySearchProvider { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     onNameMutation(domainObject, name) { | ||||
|     onMutationOfIndexedObject(domainObject) { | ||||
|         const provider = this; | ||||
|  | ||||
|         domainObject.name = name; | ||||
|         provider.index(domainObject); | ||||
|     } | ||||
|  | ||||
|     onCompositionMutation(domainObject, composition) { | ||||
|         const provider = this; | ||||
|         const indexedComposition = domainObject.composition; | ||||
|         const identifiersToIndex = composition | ||||
|             .filter(identifier => !indexedComposition | ||||
|                 .some(indexedIdentifier => this.openmct.objects | ||||
|                     .areIdsEqual([identifier, indexedIdentifier]))); | ||||
|  | ||||
|         identifiersToIndex.forEach(identifier => { | ||||
|             this.openmct.objects.get(identifier).then(objectToIndex => provider.index(objectToIndex)); | ||||
|         }); | ||||
|         provider.index(domainObject.identifier, domainObject); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Pass a domainObject to the worker to be indexed. | ||||
|      * If the object has composition, schedule those ids for later indexing. | ||||
|      * Watch for object changes and re-index object and children if so | ||||
|      * Pass an id and model to the worker to be indexed.  If the model has | ||||
|      * composition, schedule those ids for later indexing. | ||||
|      * | ||||
|      * @private | ||||
|      * @param domainObject a domainObject | ||||
|      * @param id a model id | ||||
|      * @param model a model | ||||
|      */ | ||||
|     async index(domainObject) { | ||||
|     async index(id, domainObject) { | ||||
|         const provider = this; | ||||
|         const keyString = this.openmct.objects.makeKeyString(domainObject.identifier); | ||||
|  | ||||
|         const keyString = this.openmct.objects.makeKeyString(id); | ||||
|         if (!this.indexedIds[keyString]) { | ||||
|             this.indexedIds[keyString] = this.openmct.objects.observe( | ||||
|                 domainObject, | ||||
|                 'name', | ||||
|                 this.onNameMutation.bind(this, domainObject) | ||||
|             ); | ||||
|             this.indexedCompositions[keyString] = this.openmct.objects.observe( | ||||
|                 domainObject, | ||||
|                 'composition', | ||||
|                 this.onCompositionMutation.bind(this, domainObject) | ||||
|             ); | ||||
|             this.openmct.objects.observe(domainObject, `*`, this.onMutationOfIndexedObject); | ||||
|         } | ||||
|  | ||||
|         if ((keyString !== 'ROOT')) { | ||||
|         this.indexedIds[keyString] = true; | ||||
|  | ||||
|         if ((id.key !== 'ROOT')) { | ||||
|             if (this.worker) { | ||||
|                 this.worker.port.postMessage({ | ||||
|                     request: 'index', | ||||
| @@ -273,12 +247,15 @@ class InMemorySearchProvider { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         const composition = this.openmct.composition.get(domainObject); | ||||
|         const composition = this.openmct.composition.registry.find(foundComposition => { | ||||
|             return foundComposition.appliesTo(domainObject); | ||||
|         }); | ||||
|  | ||||
|         if (composition !== undefined) { | ||||
|             const children = await composition.load(); | ||||
|  | ||||
|             children.forEach(child => provider.scheduleForIndexing(child.identifier)); | ||||
|         if (composition) { | ||||
|             const childIdentifiers = await composition.load(domainObject); | ||||
|             childIdentifiers.forEach(function (childIdentifier) { | ||||
|                 provider.scheduleForIndexing(childIdentifier); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -294,12 +271,12 @@ class InMemorySearchProvider { | ||||
|         const provider = this; | ||||
|  | ||||
|         this.pendingRequests += 1; | ||||
|         const domainObject = await this.openmct.objects.get(keyString); | ||||
|         const identifier = await this.openmct.objects.parseKeyString(keyString); | ||||
|         const domainObject = await this.openmct.objects.get(identifier.key); | ||||
|         delete provider.pendingIndex[keyString]; | ||||
|  | ||||
|         try { | ||||
|             if (domainObject) { | ||||
|                 await provider.index(domainObject); | ||||
|                 await provider.index(identifier, domainObject); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             console.warn('Failed to index domain object ' + keyString, error); | ||||
| @@ -328,9 +305,9 @@ class InMemorySearchProvider { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * A local version of the same SharedWorker function | ||||
|      * if we don't have SharedWorkers available (e.g., iOS) | ||||
|      */ | ||||
|     * A local version of the same SharedWorker function | ||||
|     * if we don't have SharedWorkers available (e.g., iOS) | ||||
|     */ | ||||
|     localIndexItem(keyString, model) { | ||||
|         this.localIndexedItems[keyString] = { | ||||
|             type: model.type, | ||||
| @@ -370,16 +347,6 @@ class InMemorySearchProvider { | ||||
|         }; | ||||
|         this.onWorkerMessage(eventToReturn); | ||||
|     } | ||||
|  | ||||
|     destroyObservers(observers) { | ||||
|         Object.entries(observers).forEach(([keyString, unobserve]) => { | ||||
|             if (typeof unobserve === 'function') { | ||||
|                 unobserve(); | ||||
|             } | ||||
|  | ||||
|             delete observers[keyString]; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default InMemorySearchProvider; | ||||
|   | ||||
| @@ -105,18 +105,13 @@ describe("The Object API Search Function", () => { | ||||
|  | ||||
|         beforeEach((done) => { | ||||
|             openmct = createOpenMct(); | ||||
|             const defaultObjectProvider = openmct.objects.getProvider({ | ||||
|                 key: '', | ||||
|                 namespace: '' | ||||
|             }); | ||||
|             openmct.objects.addProvider('foo', defaultObjectProvider); | ||||
|             spyOn(openmct.objects.inMemorySearchProvider, "query").and.callThrough(); | ||||
|             spyOn(openmct.objects.inMemorySearchProvider, "localSearch").and.callThrough(); | ||||
|  | ||||
|             openmct.on('start', async () => { | ||||
|                 mockIdentifier1 = { | ||||
|                     key: 'some-object', | ||||
|                     namespace: 'foo' | ||||
|                     namespace: 'some-namespace' | ||||
|                 }; | ||||
|                 mockDomainObject1 = { | ||||
|                     type: 'clock', | ||||
| @@ -125,7 +120,7 @@ describe("The Object API Search Function", () => { | ||||
|                 }; | ||||
|                 mockIdentifier2 = { | ||||
|                     key: 'some-other-object', | ||||
|                     namespace: 'foo' | ||||
|                     namespace: 'some-namespace' | ||||
|                 }; | ||||
|                 mockDomainObject2 = { | ||||
|                     type: 'clock', | ||||
| @@ -134,16 +129,16 @@ describe("The Object API Search Function", () => { | ||||
|                 }; | ||||
|                 mockIdentifier3 = { | ||||
|                     key: 'yet-another-object', | ||||
|                     namespace: 'foo' | ||||
|                     namespace: 'some-namespace' | ||||
|                 }; | ||||
|                 mockDomainObject3 = { | ||||
|                     type: 'clock', | ||||
|                     name: 'redBear', | ||||
|                     identifier: mockIdentifier3 | ||||
|                 }; | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockDomainObject1); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockDomainObject2); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockDomainObject3); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockIdentifier1, mockDomainObject1); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockIdentifier2, mockDomainObject2); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockIdentifier3, mockDomainObject3); | ||||
|                 done(); | ||||
|             }); | ||||
|             openmct.startHeadless(); | ||||
| @@ -180,9 +175,9 @@ describe("The Object API Search Function", () => { | ||||
|             beforeEach(async () => { | ||||
|                 openmct.objects.inMemorySearchProvider.worker = null; | ||||
|                 // reindex locally | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockDomainObject1); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockDomainObject2); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockDomainObject3); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockIdentifier1, mockDomainObject1); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockIdentifier2, mockDomainObject2); | ||||
|                 await openmct.objects.inMemorySearchProvider.index(mockIdentifier3, mockDomainObject3); | ||||
|             }); | ||||
|             it("calls local search", () => { | ||||
|                 openmct.objects.search('foo'); | ||||
|   | ||||
| @@ -22,14 +22,12 @@ | ||||
|  | ||||
| export default class Transaction { | ||||
|     constructor(objectAPI) { | ||||
|         this.dirtyObjects = {}; | ||||
|         this.dirtyObjects = new Set(); | ||||
|         this.objectAPI = objectAPI; | ||||
|     } | ||||
|  | ||||
|     add(object) { | ||||
|         const key = this.objectAPI.makeKeyString(object.identifier); | ||||
|  | ||||
|         this.dirtyObjects[key] = object; | ||||
|         this.dirtyObjects.add(object); | ||||
|     } | ||||
|  | ||||
|     cancel() { | ||||
| @@ -39,8 +37,7 @@ export default class Transaction { | ||||
|     commit() { | ||||
|         const promiseArray = []; | ||||
|         const save = this.objectAPI.save.bind(this.objectAPI); | ||||
|  | ||||
|         Object.values(this.dirtyObjects).forEach(object => { | ||||
|         this.dirtyObjects.forEach(object => { | ||||
|             promiseArray.push(this.createDirtyObjectPromise(object, save)); | ||||
|         }); | ||||
|  | ||||
| @@ -51,9 +48,7 @@ export default class Transaction { | ||||
|         return new Promise((resolve, reject) => { | ||||
|             action(object) | ||||
|                 .then((success) => { | ||||
|                     const key = this.objectAPI.makeKeyString(object.identifier); | ||||
|  | ||||
|                     delete this.dirtyObjects[key]; | ||||
|                     this.dirtyObjects.delete(object); | ||||
|                     resolve(success); | ||||
|                 }) | ||||
|                 .catch(reject); | ||||
| @@ -62,8 +57,7 @@ export default class Transaction { | ||||
|  | ||||
|     getDirtyObject(identifier) { | ||||
|         let dirtyObject; | ||||
|  | ||||
|         Object.values(this.dirtyObjects).forEach(object => { | ||||
|         this.dirtyObjects.forEach(object => { | ||||
|             const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier); | ||||
|             if (areIdsEqual) { | ||||
|                 dirtyObject = object; | ||||
| @@ -73,11 +67,14 @@ export default class Transaction { | ||||
|         return dirtyObject; | ||||
|     } | ||||
|  | ||||
|     start() { | ||||
|         this.dirtyObjects = new Set(); | ||||
|     } | ||||
|  | ||||
|     _clear() { | ||||
|         const promiseArray = []; | ||||
|         const refresh = this.objectAPI.refresh.bind(this.objectAPI); | ||||
|  | ||||
|         Object.values(this.dirtyObjects).forEach(object => { | ||||
|         this.dirtyObjects.forEach(object => { | ||||
|             promiseArray.push(this.createDirtyObjectPromise(object, refresh)); | ||||
|         }); | ||||
|  | ||||
|   | ||||
| @@ -34,24 +34,24 @@ describe("Transaction Class", () => { | ||||
|     }); | ||||
|  | ||||
|     it('has no dirty objects', () => { | ||||
|         expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); | ||||
|         expect(transaction.dirtyObjects.size).toEqual(0); | ||||
|     }); | ||||
|  | ||||
|     it('add(), adds object to dirtyObjects', () => { | ||||
|         const mockDomainObjects = createMockDomainObjects(); | ||||
|         transaction.add(mockDomainObjects[0]); | ||||
|         expect(Object.keys(transaction.dirtyObjects).length).toEqual(1); | ||||
|         expect(transaction.dirtyObjects.size).toEqual(1); | ||||
|     }); | ||||
|  | ||||
|     it('cancel(), clears all dirtyObjects', (done) => { | ||||
|         const mockDomainObjects = createMockDomainObjects(3); | ||||
|         mockDomainObjects.forEach(transaction.add.bind(transaction)); | ||||
|  | ||||
|         expect(Object.keys(transaction.dirtyObjects).length).toEqual(3); | ||||
|         expect(transaction.dirtyObjects.size).toEqual(3); | ||||
|  | ||||
|         transaction.cancel() | ||||
|             .then(success => { | ||||
|                 expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); | ||||
|                 expect(transaction.dirtyObjects.size).toEqual(0); | ||||
|             }).finally(done); | ||||
|     }); | ||||
|  | ||||
| @@ -59,12 +59,12 @@ describe("Transaction Class", () => { | ||||
|         const mockDomainObjects = createMockDomainObjects(3); | ||||
|         mockDomainObjects.forEach(transaction.add.bind(transaction)); | ||||
|  | ||||
|         expect(Object.keys(transaction.dirtyObjects).length).toEqual(3); | ||||
|         expect(transaction.dirtyObjects.size).toEqual(3); | ||||
|         spyOn(objectAPI, 'save').and.callThrough(); | ||||
|  | ||||
|         transaction.commit() | ||||
|             .then(success => { | ||||
|                 expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); | ||||
|                 expect(transaction.dirtyObjects.size).toEqual(0); | ||||
|                 expect(objectAPI.save.calls.count()).toEqual(3); | ||||
|             }).finally(done); | ||||
|     }); | ||||
| @@ -73,7 +73,7 @@ describe("Transaction Class", () => { | ||||
|         const mockDomainObjects = createMockDomainObjects(); | ||||
|         transaction.add(mockDomainObjects[0]); | ||||
|  | ||||
|         expect(Object.keys(transaction.dirtyObjects).length).toEqual(1); | ||||
|         expect(transaction.dirtyObjects.size).toEqual(1); | ||||
|         const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier); | ||||
|  | ||||
|         expect(dirtyObject).toEqual(mockDomainObjects[0]); | ||||
| @@ -82,7 +82,7 @@ describe("Transaction Class", () => { | ||||
|     it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => { | ||||
|         const mockDomainObjects = createMockDomainObjects(); | ||||
|  | ||||
|         expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); | ||||
|         expect(transaction.dirtyObjects.size).toEqual(0); | ||||
|         const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier); | ||||
|  | ||||
|         expect(dirtyObject).toEqual(undefined); | ||||
|   | ||||
| @@ -172,7 +172,6 @@ export class TelemetryCollection extends EventEmitter { | ||||
|      * @private | ||||
|      */ | ||||
|     _processNewTelemetry(telemetryData) { | ||||
|         performance.mark('tlm:process:start'); | ||||
|         if (telemetryData === undefined) { | ||||
|             return; | ||||
|         } | ||||
| @@ -353,7 +352,6 @@ export class TelemetryCollection extends EventEmitter { | ||||
|      * @todo handle subscriptions more granually | ||||
|      */ | ||||
|     _reset() { | ||||
|         performance.mark('tlm:reset'); | ||||
|         this.boundedTelemetry = []; | ||||
|         this.futureBuffer = []; | ||||
|  | ||||
|   | ||||
| @@ -365,7 +365,3 @@ class TimeContext extends EventEmitter { | ||||
| } | ||||
|  | ||||
| export default TimeContext; | ||||
|  | ||||
| /** | ||||
| @typedef {{start: number, end: number}} Bounds | ||||
| */ | ||||
|   | ||||
| @@ -15,8 +15,7 @@ export default function (folderName, couchPlugin, searchFilter) { | ||||
|                     return Promise.resolve({ | ||||
|                         identifier, | ||||
|                         type: 'folder', | ||||
|                         name: folderName || "CouchDB Documents", | ||||
|                         location: 'ROOT' | ||||
|                         name: folderName || "CouchDB Documents" | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -85,8 +85,7 @@ describe('the plugin', function () { | ||||
|             expect(object).toEqual({ | ||||
|                 identifier, | ||||
|                 type: 'folder', | ||||
|                 name: 'CouchDB Documents', | ||||
|                 location: 'ROOT' | ||||
|                 name: "CouchDB Documents" | ||||
|             }); | ||||
|         }); | ||||
|     }); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
|  | ||||
| /***************************************************************************** | ||||
|  * Open MCT, Copyright (c) 2014-2022, United States Government | ||||
|  * as represented by the Administrator of the National Aeronautics and Space | ||||
| @@ -113,12 +114,14 @@ export default { | ||||
|         this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); | ||||
|         this.formats = this.openmct.telemetry.getFormatMap(this.metadata); | ||||
|         this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); | ||||
|         this.bounds = this.openmct.time.bounds(); | ||||
|  | ||||
|         this.limitEvaluator = this.openmct | ||||
|             .telemetry | ||||
|             .limitEvaluator(this.domainObject); | ||||
|  | ||||
|         this.openmct.time.on('timeSystem', this.updateTimeSystem); | ||||
|         this.openmct.time.on('bounds', this.updateBounds); | ||||
|  | ||||
|         this.timestampKey = this.openmct.time.timeSystem().key; | ||||
|  | ||||
| @@ -132,41 +135,72 @@ export default { | ||||
|  | ||||
|         this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined; | ||||
|  | ||||
|         this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, { | ||||
|             size: 1, | ||||
|             strategy: 'latest' | ||||
|         }); | ||||
|         this.telemetryCollection.on('add', this.setLatestValues); | ||||
|         this.telemetryCollection.on('clear', this.resetValues); | ||||
|         this.telemetryCollection.load(); | ||||
|         this.unsubscribe = this.openmct | ||||
|             .telemetry | ||||
|             .subscribe(this.domainObject, this.setLatestValues); | ||||
|  | ||||
|         this.requestHistory(); | ||||
|  | ||||
|         if (this.hasUnits) { | ||||
|             this.setUnit(); | ||||
|         } | ||||
|     }, | ||||
|     destroyed() { | ||||
|         this.unsubscribe(); | ||||
|         this.openmct.time.off('timeSystem', this.updateTimeSystem); | ||||
|         this.telemetryCollection.off('add', this.setLatestValues); | ||||
|         this.telemetryCollection.off('clear', this.resetValues); | ||||
|  | ||||
|         this.telemetryCollection.destroy(); | ||||
|         this.openmct.time.off('bounds', this.updateBounds); | ||||
|     }, | ||||
|     methods: { | ||||
|         updateView() { | ||||
|             if (!this.updatingView) { | ||||
|                 this.updatingView = true; | ||||
|                 requestAnimationFrame(() => { | ||||
|                     this.timestamp = this.getParsedTimestamp(this.latestDatum); | ||||
|                     this.datum = this.latestDatum; | ||||
|                     let newTimestamp = this.getParsedTimestamp(this.latestDatum); | ||||
|  | ||||
|                     if (this.shouldUpdate(newTimestamp)) { | ||||
|                         this.timestamp = newTimestamp; | ||||
|                         this.datum = this.latestDatum; | ||||
|                     } | ||||
|  | ||||
|                     this.updatingView = false; | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         setLatestValues(data) { | ||||
|             this.latestDatum = data[data.length - 1]; | ||||
|         setLatestValues(datum) { | ||||
|             this.latestDatum = datum; | ||||
|  | ||||
|             this.updateView(); | ||||
|         }, | ||||
|         shouldUpdate(newTimestamp) { | ||||
|             return this.inBounds(newTimestamp) | ||||
|                 && (this.timestamp === undefined || newTimestamp > this.timestamp); | ||||
|         }, | ||||
|         requestHistory() { | ||||
|             this.openmct | ||||
|                 .telemetry | ||||
|                 .request(this.domainObject, { | ||||
|                     start: this.bounds.start, | ||||
|                     end: this.bounds.end, | ||||
|                     size: 1, | ||||
|                     strategy: 'latest' | ||||
|                 }) | ||||
|                 .then((array) => this.setLatestValues(array[array.length - 1])) | ||||
|                 .catch((error) => { | ||||
|                     console.warn('Error fetching data', error); | ||||
|                 }); | ||||
|         }, | ||||
|         updateBounds(bounds, isTick) { | ||||
|             this.bounds = bounds; | ||||
|             if (!isTick) { | ||||
|                 this.resetValues(); | ||||
|                 this.requestHistory(); | ||||
|             } | ||||
|         }, | ||||
|         inBounds(timestamp) { | ||||
|             return timestamp >= this.bounds.start && timestamp <= this.bounds.end; | ||||
|         }, | ||||
|         updateTimeSystem(timeSystem) { | ||||
|             this.resetValues(); | ||||
|             this.timestampKey = timeSystem.key; | ||||
|         }, | ||||
|         updateViewContext() { | ||||
| @@ -207,3 +241,4 @@ export default { | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
|   | ||||
| @@ -46,7 +46,6 @@ describe("The LAD Table", () => { | ||||
|  | ||||
|     let openmct; | ||||
|     let ladPlugin; | ||||
|     let historicalProvider; | ||||
|     let parent; | ||||
|     let child; | ||||
|     let telemetryCount = 3; | ||||
| @@ -82,13 +81,6 @@ describe("The LAD Table", () => { | ||||
|  | ||||
|         spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({})); | ||||
|  | ||||
|         historicalProvider = { | ||||
|             request: () => { | ||||
|                 return Promise.resolve([]); | ||||
|             } | ||||
|         }; | ||||
|         spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider); | ||||
|  | ||||
|         openmct.time.bounds({ | ||||
|             start: bounds.start, | ||||
|             end: bounds.end | ||||
| @@ -155,7 +147,7 @@ describe("The LAD Table", () => { | ||||
|         // add another telemetry object as composition in lad table to test multi rows | ||||
|         mockObj.ladTable.composition.push(anotherTelemetryObj.identifier); | ||||
|  | ||||
|         beforeEach(async (done) => { | ||||
|         beforeEach(async () => { | ||||
|             let telemetryRequestResolve; | ||||
|             let telemetryObjectResolve; | ||||
|             let anotherTelemetryObjectResolve; | ||||
| @@ -174,12 +166,11 @@ describe("The LAD Table", () => { | ||||
|                 callBack(); | ||||
|             }); | ||||
|  | ||||
|             historicalProvider.request = () => { | ||||
|             openmct.telemetry.request.and.callFake(() => { | ||||
|                 telemetryRequestResolve(mockTelemetry); | ||||
|  | ||||
|                 return telemetryRequestPromise; | ||||
|             }; | ||||
|  | ||||
|             }); | ||||
|             openmct.objects.get.and.callFake((obj) => { | ||||
|                 if (obj.key === 'telemetry-object') { | ||||
|                     telemetryObjectResolve(mockObj.telemetry); | ||||
| @@ -204,8 +195,6 @@ describe("The LAD Table", () => { | ||||
|  | ||||
|             await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]); | ||||
|             await Vue.nextTick(); | ||||
|  | ||||
|             done(); | ||||
|         }); | ||||
|  | ||||
|         it("should show one row per object in the composition", () => { | ||||
|   | ||||
| @@ -1,34 +1,28 @@ | ||||
| <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> | ||||
|   | ||||
| @@ -21,13 +21,12 @@ | ||||
| --> | ||||
|  | ||||
| <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> | ||||
|  | ||||
|   | ||||
| @@ -22,13 +22,11 @@ | ||||
| <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> | ||||
|   | ||||
| @@ -21,14 +21,12 @@ | ||||
| --> | ||||
| <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> | ||||
|  | ||||
| @@ -38,15 +36,14 @@ | ||||
|             <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> | ||||
|   | ||||
| @@ -185,14 +185,10 @@ describe('The Clear Data Plugin:', () => { | ||||
|         beforeEach((done) => { | ||||
|             openmct = createOpenMct(); | ||||
|  | ||||
|             clearDataPlugin = new ClearDataPlugin([ | ||||
|                 'table', | ||||
|                 'telemetry.plot.overlay', | ||||
|                 'telemetry.plot.stacked', | ||||
|                 'example.imagery' | ||||
|             ], { | ||||
|                 indicator: true | ||||
|             }); | ||||
|             clearDataPlugin = new ClearDataPlugin( | ||||
|                 ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'], | ||||
|                 {indicator: true} | ||||
|             ); | ||||
|             openmct.install(clearDataPlugin); | ||||
|             appHolder = document.createElement('div'); | ||||
|             document.body.appendChild(appHolder); | ||||
|   | ||||
| @@ -21,35 +21,31 @@ | ||||
| *****************************************************************************/ | ||||
|  | ||||
| <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> | ||||
| @@ -58,123 +54,107 @@ | ||||
|                     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" | ||||
| @@ -186,10 +166,9 @@ | ||||
|             </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"> | ||||
| @@ -201,9 +180,8 @@ | ||||
|             </span> | ||||
|         </div> | ||||
|         <div class="c-condition__summary"> | ||||
|             <condition-description | ||||
|                 :show-label="false" | ||||
|                 :condition="condition" | ||||
|             <condition-description :show-label="false" | ||||
|                                    :condition="condition" | ||||
|             /> | ||||
|         </div> | ||||
|     </div> | ||||
|   | ||||
| @@ -21,9 +21,8 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| <template> | ||||
| <section | ||||
|     id="conditionCollection" | ||||
|     :class="{ 'is-expanded': expanded }" | ||||
| <section id="conditionCollection" | ||||
|          :class="{ 'is-expanded': expanded }" | ||||
| > | ||||
|     <div class="c-cs__header c-section__header"> | ||||
|         <span | ||||
| @@ -33,14 +32,12 @@ | ||||
|         ></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> | ||||
| @@ -55,26 +52,24 @@ | ||||
|             <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> | ||||
|   | ||||
| @@ -22,21 +22,18 @@ | ||||
|  | ||||
| <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> | ||||
|   | ||||
| @@ -21,14 +21,12 @@ | ||||
| *****************************************************************************/ | ||||
|  | ||||
| <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> | ||||
|   | ||||
| @@ -36,20 +36,18 @@ | ||||
|         </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> | ||||
|   | ||||
| @@ -26,89 +26,76 @@ | ||||
|     <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> | ||||
|   | ||||
| @@ -21,10 +21,9 @@ | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| <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 | ||||
| @@ -34,13 +33,11 @@ | ||||
|         ></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 | ||||
| @@ -53,69 +50,59 @@ | ||||
|             </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> | ||||
|   | ||||
| @@ -23,60 +23,52 @@ | ||||
| <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> | ||||
|   | ||||
| @@ -22,9 +22,8 @@ | ||||
|  | ||||
| <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> | ||||
| @@ -38,18 +37,16 @@ | ||||
|             @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 | ||||
| @@ -67,10 +64,9 @@ | ||||
|             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> | ||||
| @@ -84,17 +80,15 @@ | ||||
|                     <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 | ||||
| @@ -106,9 +100,8 @@ | ||||
|                 <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> | ||||
| @@ -121,32 +114,27 @@ | ||||
|             @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> | ||||
| @@ -337,7 +325,16 @@ export default { | ||||
|             return item && (item.type === type); | ||||
|         }, | ||||
|         canPersistObject(item) { | ||||
|             return this.openmct.objects.isPersistable(item.identifier); | ||||
|             // 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; | ||||
|         }, | ||||
|         hasConditionalStyle(domainObject, layoutItem) { | ||||
|             const id = layoutItem ? layoutItem.id : undefined; | ||||
|   | ||||
| @@ -21,13 +21,12 @@ | ||||
| *****************************************************************************/ | ||||
|  | ||||
| <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"> | ||||
|         {{ label }} | ||||
|         {{ internalDomainObject.conditionalLabel || internalDomainObject.label }} | ||||
|     </div> | ||||
| </component> | ||||
| </template> | ||||
| @@ -39,112 +38,28 @@ export default { | ||||
|     inject: ['openmct', 'domainObject'], | ||||
|     data: function () { | ||||
|         return { | ||||
|             conditionalLabel: '', | ||||
|             conditionSetIdentifier: null, | ||||
|             domainObjectLabel: '', | ||||
|             url: null, | ||||
|             urlDefined: false, | ||||
|             useConditionSetOutputAsLabel: false | ||||
|             internalDomainObject: this.domainObject | ||||
|         }; | ||||
|     }, | ||||
|     computed: { | ||||
|         label() { | ||||
|             return this.useConditionSetOutputAsLabel | ||||
|                 ? this.conditionalLabel | ||||
|                 : this.domainObjectLabel | ||||
|             ; | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         conditionSetIdentifier: { | ||||
|             handler(newValue, oldValue) { | ||||
|                 if (!oldValue || !newValue || !this.openmct.objects.areIdsEqual(newValue, oldValue)) { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 this.listenToConditionSetChanges(); | ||||
|             }, | ||||
|             deep: true | ||||
|         urlDefined() { | ||||
|             return this.internalDomainObject.url && this.internalDomainObject.url.length > 0; | ||||
|         }, | ||||
|         url() { | ||||
|             return this.urlDefined ? sanitizeUrl(this.internalDomainObject.url) : null; | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject); | ||||
|  | ||||
|         if (this.domainObject) { | ||||
|             this.updateDomainObject(this.domainObject); | ||||
|             this.listenToConditionSetChanges(); | ||||
|         } | ||||
|         this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.conditionSetIdentifier = null; | ||||
|  | ||||
|         if (this.unlisten) { | ||||
|             this.unlisten(); | ||||
|         } | ||||
|  | ||||
|         this.stopListeningToConditionSetChanges(); | ||||
|     }, | ||||
|     methods: { | ||||
|         async listenToConditionSetChanges() { | ||||
|             if (!this.conditionSetIdentifier) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             const conditionSetDomainObject = await this.openmct.objects.get(this.conditionSetIdentifier); | ||||
|             this.stopListeningToConditionSetChanges(); | ||||
|  | ||||
|             if (!conditionSetDomainObject) { | ||||
|                 this.openmct.notifications.alert('Unable to find condition set'); | ||||
|             } | ||||
|  | ||||
|             this.telemetryCollection = this.openmct.telemetry.requestCollection(conditionSetDomainObject, { | ||||
|                 size: 1, | ||||
|                 strategy: 'latest' | ||||
|             }); | ||||
|  | ||||
|             this.telemetryCollection.on('add', this.updateConditionLabel, this); | ||||
|             this.telemetryCollection.load(); | ||||
|         }, | ||||
|         stopListeningToConditionSetChanges() { | ||||
|             if (this.telemetryCollection) { | ||||
|                 this.telemetryCollection.off('add', this.updateConditionLabel, this); | ||||
|                 this.telemetryCollection.destroy(); | ||||
|                 this.telemetryCollection = null; | ||||
|             } | ||||
|         }, | ||||
|         updateConditionLabel([latestDatum]) { | ||||
|             if (!this.conditionSetIdentifier) { | ||||
|                 this.stopListeningToConditionSetChanges(); | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.conditionalLabel = latestDatum.output || ''; | ||||
|         }, | ||||
|         updateDomainObject(domainObject) { | ||||
|             if (this.domainObjectLabel !== domainObject.label) { | ||||
|                 this.domainObjectLabel = domainObject.label; | ||||
|             } | ||||
|  | ||||
|             const urlDefined = domainObject.url && domainObject.url.length > 0; | ||||
|             if (this.urlDefined !== urlDefined) { | ||||
|                 this.urlDefined = urlDefined; | ||||
|             } | ||||
|  | ||||
|             const url = this.urlDefined ? sanitizeUrl(domainObject.url) : null; | ||||
|             if (this.url !== url) { | ||||
|                 this.url = url; | ||||
|             } | ||||
|  | ||||
|             const conditionSetIdentifier = domainObject.configuration.objectStyles.conditionSetIdentifier; | ||||
|             if (this.conditionSetIdentifier !== conditionSetIdentifier) { | ||||
|                 this.conditionSetIdentifier = conditionSetIdentifier; | ||||
|             } | ||||
|  | ||||
|             const useConditionSetOutputAsLabel = this.conditionSetIdentifier && domainObject.configuration.useConditionSetOutputAsLabel; | ||||
|             if (this.useConditionSetOutputAsLabel !== useConditionSetOutputAsLabel) { | ||||
|                 this.useConditionSetOutputAsLabel = useConditionSetOutputAsLabel; | ||||
|             } | ||||
|         updateInternalDomainObject(domainObject) { | ||||
|             this.internalDomainObject = domainObject; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|   | ||||
| @@ -166,7 +166,7 @@ export default { | ||||
|     }, | ||||
|     computed: { | ||||
|         gridSize() { | ||||
|             return this.domainObject.configuration.layoutGrid.map(Number); | ||||
|             return this.domainObject.configuration.layoutGrid; | ||||
|         }, | ||||
|         layoutItems() { | ||||
|             return this.domainObject.configuration.items; | ||||
|   | ||||
| @@ -37,9 +37,8 @@ | ||||
|         :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" | ||||
| @@ -222,20 +221,20 @@ export default { | ||||
|                 .then(this.setObject); | ||||
|         } | ||||
|  | ||||
|         this.openmct.time.on("bounds", this.refreshData); | ||||
|  | ||||
|         this.status = this.openmct.status.get(this.item.identifier); | ||||
|         this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus); | ||||
|     }, | ||||
|     beforeDestroy() { | ||||
|         this.removeSubscription(); | ||||
|         this.removeStatusListener(); | ||||
|  | ||||
|         if (this.removeSelectable) { | ||||
|             this.removeSelectable(); | ||||
|         } | ||||
|  | ||||
|         this.telemetryCollection.off('add', this.setLatestValues); | ||||
|         this.telemetryCollection.off('clear', this.refreshData); | ||||
|  | ||||
|         this.telemetryCollection.destroy(); | ||||
|         this.openmct.time.off("bounds", this.refreshData); | ||||
|  | ||||
|         if (this.mutablePromise) { | ||||
|             this.mutablePromise.then(() => { | ||||
| @@ -253,9 +252,34 @@ export default { | ||||
|  | ||||
|             return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`; | ||||
|         }, | ||||
|         setLatestValues(data) { | ||||
|             this.latestDatum = data[data.length - 1]; | ||||
|             this.updateView(); | ||||
|         requestHistoricalData() { | ||||
|             let bounds = this.openmct.time.bounds(); | ||||
|             let options = { | ||||
|                 start: bounds.start, | ||||
|                 end: bounds.end, | ||||
|                 size: 1, | ||||
|                 strategy: 'latest' | ||||
|             }; | ||||
|             this.openmct.telemetry.request(this.domainObject, options) | ||||
|                 .then(data => { | ||||
|                     if (data.length > 0) { | ||||
|                         this.latestDatum = data[data.length - 1]; | ||||
|                         this.updateView(); | ||||
|                     } | ||||
|                 }); | ||||
|         }, | ||||
|         subscribeToObject() { | ||||
|             this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) { | ||||
|                 const key = this.openmct.time.timeSystem().key; | ||||
|                 const datumTimeStamp = datum[key]; | ||||
|                 if (this.openmct.time.clock() !== undefined | ||||
|                     || (datumTimeStamp | ||||
|                         && (this.openmct.time.bounds().end >= datumTimeStamp)) | ||||
|                 ) { | ||||
|                     this.latestDatum = datum; | ||||
|                     this.updateView(); | ||||
|                 } | ||||
|             }.bind(this)); | ||||
|         }, | ||||
|         updateView() { | ||||
|             if (!this.updatingView) { | ||||
| @@ -266,10 +290,17 @@ export default { | ||||
|                 }); | ||||
|             } | ||||
|         }, | ||||
|         removeSubscription() { | ||||
|             if (this.subscription) { | ||||
|                 this.subscription(); | ||||
|                 this.subscription = undefined; | ||||
|             } | ||||
|         }, | ||||
|         refreshData(bounds, isTick) { | ||||
|             if (!isTick) { | ||||
|                 this.latestDatum = undefined; | ||||
|                 this.updateView(); | ||||
|                 this.requestHistoricalData(this.domainObject); | ||||
|             } | ||||
|         }, | ||||
|         setObject(domainObject) { | ||||
| @@ -283,13 +314,8 @@ export default { | ||||
|             const valueMetadata = this.metadata.value(this.item.value); | ||||
|             this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format); | ||||
|  | ||||
|             this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, { | ||||
|                 size: 1, | ||||
|                 strategy: 'latest' | ||||
|             }); | ||||
|             this.telemetryCollection.on('add', this.setLatestValues); | ||||
|             this.telemetryCollection.on('clear', this.refreshData); | ||||
|             this.telemetryCollection.load(); | ||||
|             this.requestHistoricalData(); | ||||
|             this.subscribeToObject(); | ||||
|  | ||||
|             this.currentObjectPath = this.objectPath.slice(); | ||||
|             this.currentObjectPath.unshift(this.domainObject); | ||||
|   | ||||
| @@ -97,16 +97,13 @@ export default class DuplicateAction { | ||||
|  | ||||
|     validate(currentParent) { | ||||
|         return (data) => { | ||||
|             const parentCandidate = data.value[0]; | ||||
|             const parentCandidatePath = data.value; | ||||
|             const parentCandidate = parentCandidatePath[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; | ||||
|             } | ||||
| @@ -125,14 +122,13 @@ export default class DuplicateAction { | ||||
|     } | ||||
|  | ||||
|     appliesTo(objectPath) { | ||||
|         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); | ||||
|         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; | ||||
|  | ||||
|         if (locked || !isPersistable) { | ||||
|         if (locked) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -52,7 +52,7 @@ export default class ExportAsJSONAction { | ||||
|     appliesTo(objectPath) { | ||||
|         let domainObject = objectPath[0]; | ||||
|  | ||||
|         return this._isCreatableAndPersistable(domainObject); | ||||
|         return this._isCreatable(domainObject); | ||||
|     } | ||||
|     /** | ||||
|      * | ||||
| @@ -80,11 +80,10 @@ export default class ExportAsJSONAction { | ||||
|      * @param {object} domainObject | ||||
|      * @returns {boolean} | ||||
|      */ | ||||
|     _isCreatableAndPersistable(domainObject) { | ||||
|     _isCreatable(domainObject) { | ||||
|         const type = this.openmct.types.get(domainObject.type); | ||||
|         const isPersistable = this.openmct.objects.isPersistable(domainObject.identifier); | ||||
|  | ||||
|         return type && type.definition.creatable && isPersistable; | ||||
|         return type && type.definition.creatable; | ||||
|     } | ||||
|     /** | ||||
|      * @private | ||||
| @@ -171,7 +170,7 @@ export default class ExportAsJSONAction { | ||||
|                 .then((children) => { | ||||
|                     children.forEach((child, index) => { | ||||
|                         // Only export if object is creatable | ||||
|                         if (this._isCreatableAndPersistable(child)) { | ||||
|                         if (this._isCreatable(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 | ||||
|   | ||||
| @@ -27,10 +27,6 @@ describe('Export as JSON plugin', () => { | ||||
|  | ||||
|     it('ExportAsJSONAction applies to folder', () => { | ||||
|         domainObject = { | ||||
|             identifier: { | ||||
|                 key: 'export-testing', | ||||
|                 namespace: '' | ||||
|             }, | ||||
|             composition: [], | ||||
|             location: 'mine', | ||||
|             modified: 1640115501237, | ||||
| @@ -44,10 +40,6 @@ describe('Export as JSON plugin', () => { | ||||
|  | ||||
|     it('ExportAsJSONAction applies to telemetry.plot.overlay', () => { | ||||
|         domainObject = { | ||||
|             identifier: { | ||||
|                 key: 'export-testing', | ||||
|                 namespace: '' | ||||
|             }, | ||||
|             composition: [], | ||||
|             location: 'mine', | ||||
|             modified: 1640115501237, | ||||
| @@ -61,10 +53,6 @@ describe('Export as JSON plugin', () => { | ||||
|  | ||||
|     it('ExportAsJSONAction applies to telemetry.plot.stacked', () => { | ||||
|         domainObject = { | ||||
|             identifier: { | ||||
|                 key: 'export-testing', | ||||
|                 namespace: '' | ||||
|             }, | ||||
|             composition: [], | ||||
|             location: 'mine', | ||||
|             modified: 1640115501237, | ||||
| @@ -76,24 +64,16 @@ describe('Export as JSON plugin', () => { | ||||
|         expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true); | ||||
|     }); | ||||
|  | ||||
|     it('ExportAsJSONAction does not applie to non-persistable objects', () => { | ||||
|     it('ExportAsJSONAction applies does not applies to non-creatable objects', () => { | ||||
|         domainObject = { | ||||
|             identifier: { | ||||
|                 key: 'export-testing', | ||||
|                 namespace: '' | ||||
|             }, | ||||
|             composition: [], | ||||
|             location: 'mine', | ||||
|             modified: 1640115501237, | ||||
|             name: 'Non Editable Folder', | ||||
|             persisted: 1640115501237, | ||||
|             type: 'folder' | ||||
|             type: 'noneditable.folder' | ||||
|         }; | ||||
|  | ||||
|         spyOn(openmct.objects, 'getProvider').and.callFake(() => { | ||||
|             return { get: () => domainObject }; | ||||
|         }); | ||||
|  | ||||
|         expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(false); | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -57,7 +57,7 @@ | ||||
|             /> | ||||
|  | ||||
|             <drop-hint | ||||
|                 :key="'hint-' + i" | ||||
|                 :key="i" | ||||
|                 class="c-fl-frame__drop-hint" | ||||
|                 :index="i" | ||||
|                 :allow-drop="allowDrop" | ||||
| @@ -66,7 +66,7 @@ | ||||
|  | ||||
|             <resize-handle | ||||
|                 v-if="(i !== frames.length - 1)" | ||||
|                 :key="'handle-' + i" | ||||
|                 :key="i" | ||||
|                 :index="i" | ||||
|                 :orientation="rowsLayout ? 'horizontal' : 'vertical'" | ||||
|                 :is-editing="isEditing" | ||||
|   | ||||
| @@ -43,7 +43,7 @@ | ||||
|         <template v-for="(container, index) in containers"> | ||||
|             <drop-hint | ||||
|                 v-if="index === 0 && containers.length > 1" | ||||
|                 :key="`hint-top-${container.id}`" | ||||
|                 :key="index" | ||||
|                 class="c-fl-frame__drop-hint" | ||||
|                 :index="-1" | ||||
|                 :allow-drop="allowContainerDrop" | ||||
| @@ -51,7 +51,7 @@ | ||||
|             /> | ||||
|  | ||||
|             <container-component | ||||
|                 :key="`component-${container.id}`" | ||||
|                 :key="container.id" | ||||
|                 class="c-fl__container" | ||||
|                 :index="index" | ||||
|                 :container="container" | ||||
| @@ -66,7 +66,7 @@ | ||||
|  | ||||
|             <resize-handle | ||||
|                 v-if="index !== (containers.length - 1)" | ||||
|                 :key="`handle-${container.id}`" | ||||
|                 :key="index" | ||||
|                 :index="index" | ||||
|                 :orientation="rowsLayout ? 'vertical' : 'horizontal'" | ||||
|                 :is-editing="isEditing" | ||||
| @@ -77,7 +77,7 @@ | ||||
|  | ||||
|             <drop-hint | ||||
|                 v-if="containers.length > 1" | ||||
|                 :key="`hint-bottom-${container.id}`" | ||||
|                 :key="index" | ||||
|                 class="c-fl-frame__drop-hint" | ||||
|                 :index="index" | ||||
|                 :allow-drop="allowContainerDrop" | ||||
|   | ||||
| @@ -26,9 +26,8 @@ | ||||
|         </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" | ||||
|   | ||||
| @@ -17,9 +17,8 @@ | ||||
|                 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> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
| <div class="c-table c-table--sortable c-list-view c-list-view--sticky-header c-list-view--selectable"> | ||||
| <div class="c-table c-table--sortable c-list-view"> | ||||
|     <table class="c-table__body"> | ||||
|         <thead class="c-table__header"> | ||||
|             <tr> | ||||
|   | ||||
							
								
								
									
										32
									
								
								src/plugins/folderView/components/list-view.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/plugins/folderView/components/list-view.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| /******************************* LIST VIEW */ | ||||
| .c-list-view { | ||||
|     overflow-x: auto !important; | ||||
|     overflow-y: auto; | ||||
|  | ||||
|     tbody tr { | ||||
|         background: $colorListItemBg; | ||||
|         transition: $transOut; | ||||
|     } | ||||
|  | ||||
|     body.desktop & { | ||||
|         tbody tr { | ||||
|             cursor: pointer; | ||||
|  | ||||
|             &:hover { | ||||
|                 background: $colorListItemBgHov; | ||||
|                 filter: $filterHov; | ||||
|                 transition: $transIn; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     td { | ||||
|         $p: floor($interiorMargin * 1.5); | ||||
|         @include ellipsize(); | ||||
|         line-height: 120%; // Needed for icon alignment | ||||
|         max-width: 0; | ||||
|         padding-top: $p; | ||||
|         padding-bottom: $p; | ||||
|         width: 25%; | ||||
|     } | ||||
| } | ||||
| @@ -90,9 +90,6 @@ export default class CreateWizard { | ||||
|             rows: this.properties.map(property => { | ||||
|                 const row = JSON.parse(JSON.stringify(property)); | ||||
|                 row.value = this.getValue(row); | ||||
|                 if (property.validate) { | ||||
|                     row.validate = property.validate; | ||||
|                 } | ||||
|  | ||||
|                 return row; | ||||
|             }).filter(row => row && row.control) | ||||
| @@ -104,10 +101,7 @@ export default class CreateWizard { | ||||
|         // Ensure there is always a 'save in' section | ||||
|         if (includeLocation) { | ||||
|             function validateLocation(data) { | ||||
|                 const policyCheck = self.openmct.composition.checkPolicy(data.value[0], domainObject); | ||||
|                 const parentIsPersistable = self.openmct.objects.isPersistable(data.value[0].identifier); | ||||
|  | ||||
|                 return policyCheck && parentIsPersistable; | ||||
|                 return self.openmct.composition.checkPolicy(data.value[0], domainObject); | ||||
|             } | ||||
|  | ||||
|             sections.push({ | ||||
|   | ||||
| @@ -51,29 +51,41 @@ export default class EditPropertiesAction extends PropertiesAction { | ||||
|     /** | ||||
|      * @private | ||||
|      */ | ||||
|     _onSave(changes) { | ||||
|         try { | ||||
|             Object.entries(changes).forEach(([key, value]) => { | ||||
|                 const properties = key.split('.'); | ||||
|                 let object = this.domainObject; | ||||
|                 const propertiesLength = properties.length; | ||||
|                 properties.forEach((property, index) => { | ||||
|                     const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1; | ||||
|                     if (isComplexProperty && object[property] !== null) { | ||||
|                         object = object[property]; | ||||
|                     } else { | ||||
|                         object[property] = value; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 object = value; | ||||
|                 this.openmct.objects.mutate(this.domainObject, key, value); | ||||
|                 this.openmct.notifications.info('Save successful'); | ||||
|     async _onSave(changes) { | ||||
|         Object.entries(changes).forEach(([key, value]) => { | ||||
|             const properties = key.split('.'); | ||||
|             let object = this.domainObject; | ||||
|             const propertiesLength = properties.length; | ||||
|             properties.forEach((property, index) => { | ||||
|                 const isComplexProperty = propertiesLength > 1 && index !== propertiesLength - 1; | ||||
|                 if (isComplexProperty && object[property] !== null) { | ||||
|                     object = object[property]; | ||||
|                 } else { | ||||
|                     object[property] = value; | ||||
|                 } | ||||
|             }); | ||||
|         } catch (error) { | ||||
|  | ||||
|             object = value; | ||||
|         }); | ||||
|  | ||||
|         this.domainObject.modified = Date.now(); | ||||
|  | ||||
|         // Show saving progress dialog | ||||
|         let dialog = this.openmct.overlays.progressDialog({ | ||||
|             progressPerc: 'unknown', | ||||
|             message: 'Do not navigate away from this page or close this browser tab while this message is displayed.', | ||||
|             iconClass: 'info', | ||||
|             title: 'Saving' | ||||
|         }); | ||||
|  | ||||
|         const success = await this.openmct.objects.save(this.domainObject); | ||||
|         if (success) { | ||||
|             this.openmct.notifications.info('Save successful'); | ||||
|         } else { | ||||
|             this.openmct.notifications.error('Error saving objects'); | ||||
|             console.error(error); | ||||
|         } | ||||
|  | ||||
|         dialog.dismiss(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user