mirror of
https://github.com/nasa/openmct.git
synced 2025-07-01 21:01:11 +00:00
Compare commits
106 Commits
v2.0.0
...
condition-
Author | SHA1 | Date | |
---|---|---|---|
bfa604efa0 | |||
e75befafbd | |||
d7d06b59ea | |||
8b4a55a7ec | |||
2519e601d7 | |||
b7b205621b | |||
3d2d932323 | |||
ce28dd2b9f | |||
286a533dad | |||
378a4ca282 | |||
fff3ce0acf | |||
db5cb2517f | |||
5236f1c796 | |||
1ed253cb07 | |||
a6553ba010 | |||
cf6bc5be2d | |||
a53a3a0297 | |||
402cd15726 | |||
a5580912e3 | |||
54d1b8991c | |||
7b6acee793 | |||
04e1c60e5c | |||
91bcd78d40 | |||
a3c0e073c8 | |||
21ae9f45c1 | |||
0a40c8dd0b | |||
ef1ea8e712 | |||
5c4fad77ff | |||
dbac9e6cd2 | |||
4b7bcf9c89 | |||
2b42abd495 | |||
1f2102b845 | |||
2ccb90aa41 | |||
525496fbca | |||
47099786cb | |||
3a11291a3b | |||
476f1b2579 | |||
6153ad8e1e | |||
77c0b16050 | |||
d19088cec6 | |||
43afb39e56 | |||
cd8c332fb5 | |||
b899475939 | |||
cc1f7659f9 | |||
0d5539be96 | |||
0a511e6155 | |||
47b6d19de8 | |||
3fd93f47bc | |||
651e61954c | |||
d30ec4c757 | |||
3c24733476 | |||
04d00fac3d | |||
150909d4b9 | |||
2b599a7ff4 | |||
824a597825 | |||
cf5edf2db0 | |||
0705d321da | |||
dad0768d57 | |||
9643dbe918 | |||
594f9d3e9a | |||
0f9e727675 | |||
0cf30940c8 | |||
3a2381b90b | |||
94def56bcb | |||
93a796353d | |||
18ff16052a | |||
f1c7fa337d | |||
557f29f9bb | |||
255ebc9a37 | |||
ca7fbe58e3 | |||
b347f35892 | |||
28d5d72834 | |||
0df33730f7 | |||
7b2ff8fa15 | |||
d80b692354 | |||
4205abdc80 | |||
492ff2fa64 | |||
93a81a1369 | |||
482d8f392c | |||
67234c70a4 | |||
e9680e975f | |||
e691a89682 | |||
bcd668594d | |||
5471e13d9e | |||
dae446808e | |||
ee9e47f487 | |||
003b1ffede | |||
16cb5f3911 | |||
74b83903c9 | |||
6a470fba1a | |||
4e7debabb1 | |||
384e36920c | |||
d4429f9686 | |||
0b2d08078b | |||
3bbc9e1582 | |||
af0420361b | |||
896f0ca3f4 | |||
28c5405a01 | |||
d114353556 | |||
f40398807e | |||
81f440e1e6 | |||
c6d6400131 | |||
55828af1ec | |||
34b951f4c6 | |||
a7d4006fee | |||
a71485f820 |
@ -2,7 +2,7 @@ version: 2.1
|
|||||||
executors:
|
executors:
|
||||||
pw-focal-development:
|
pw-focal-development:
|
||||||
docker:
|
docker:
|
||||||
- image: mcr.microsoft.com/playwright:v1.18.1-focal
|
- image: mcr.microsoft.com/playwright:v1.19.2-focal
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||||
parameters:
|
parameters:
|
||||||
@ -76,7 +76,7 @@ jobs:
|
|||||||
node-version: <<parameters.node-version>>
|
node-version: <<parameters.node-version>>
|
||||||
- run: npm audit --audit-level=low
|
- run: npm audit --audit-level=low
|
||||||
- generate_and_store_version_and_filesystem_artifacts
|
- generate_and_store_version_and_filesystem_artifacts
|
||||||
node14-lint:
|
lint:
|
||||||
parameters:
|
parameters:
|
||||||
node-version:
|
node-version:
|
||||||
type: string
|
type: string
|
||||||
@ -101,7 +101,7 @@ jobs:
|
|||||||
equal: [ "FirefoxESR", <<parameters.browser>> ]
|
equal: [ "FirefoxESR", <<parameters.browser>> ]
|
||||||
steps:
|
steps:
|
||||||
- browser-tools/install-firefox:
|
- browser-tools/install-firefox:
|
||||||
version: "91.4.0esr" #https://archive.mozilla.org/pub/firefox/releases/
|
version: "91.7.1esr" #https://archive.mozilla.org/pub/firefox/releases/
|
||||||
- when:
|
- when:
|
||||||
condition:
|
condition:
|
||||||
equal: [ "FirefoxHeadless", <<parameters.browser>> ]
|
equal: [ "FirefoxHeadless", <<parameters.browser>> ]
|
||||||
@ -113,7 +113,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- browser-tools/install-chrome:
|
- browser-tools/install-chrome:
|
||||||
replace-existing: false
|
replace-existing: false
|
||||||
- run: npm run test:coverage -- --browsers=<<parameters.browser>>
|
- run: npm run test -- --browsers=<<parameters.browser>>
|
||||||
- save_cache_cmd:
|
- save_cache_cmd:
|
||||||
node-version: <<parameters.node-version>>
|
node-version: <<parameters.node-version>>
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
@ -141,32 +141,33 @@ jobs:
|
|||||||
workflows:
|
workflows:
|
||||||
overall-circleci-commit-status: #These jobs run on every commit
|
overall-circleci-commit-status: #These jobs run on every commit
|
||||||
jobs:
|
jobs:
|
||||||
- node14-lint:
|
- lint:
|
||||||
node-version: lts/fermium
|
name: node16-lint
|
||||||
- unit-test:
|
node-version: lts/gallium
|
||||||
name: node12-chrome
|
|
||||||
node-version: lts/erbium
|
|
||||||
browser: ChromeHeadless
|
|
||||||
- unit-test:
|
- unit-test:
|
||||||
name: node14-chrome
|
name: node14-chrome
|
||||||
node-version: lts/fermium
|
node-version: lts/fermium
|
||||||
browser: ChromeHeadless
|
browser: ChromeHeadless
|
||||||
post-steps:
|
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
|
||||||
- e2e-test:
|
- e2e-test:
|
||||||
name: e2e-ci
|
name: e2e-ci
|
||||||
node-version: lts/fermium
|
node-version: lts/gallium
|
||||||
suite: ci
|
suite: ci
|
||||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||||
jobs:
|
jobs:
|
||||||
- unit-test:
|
- unit-test:
|
||||||
name: node12-firefoxESR-nightly
|
name: node16-firefoxESR-nightly
|
||||||
node-version: lts/erbium
|
node-version: lts/gallium
|
||||||
browser: FirefoxESR
|
browser: FirefoxESR
|
||||||
- unit-test:
|
|
||||||
name: node12-chrome-nightly
|
|
||||||
node-version: lts/erbium
|
|
||||||
browser: ChromeHeadless
|
|
||||||
- unit-test:
|
- unit-test:
|
||||||
name: node14-firefox-nightly
|
name: node14-firefox-nightly
|
||||||
node-version: lts/fermium
|
node-version: lts/fermium
|
||||||
@ -175,11 +176,19 @@ workflows:
|
|||||||
name: node14-chrome-nightly
|
name: node14-chrome-nightly
|
||||||
node-version: lts/fermium
|
node-version: lts/fermium
|
||||||
browser: ChromeHeadless
|
browser: ChromeHeadless
|
||||||
|
- unit-test:
|
||||||
|
name: node16-chrome-nightly
|
||||||
|
node-version: lts/gallium
|
||||||
|
browser: ChromeHeadless
|
||||||
|
- unit-test:
|
||||||
|
name: node18-chrome
|
||||||
|
node-version: "18"
|
||||||
|
browser: ChromeHeadless
|
||||||
- npm-audit:
|
- npm-audit:
|
||||||
node-version: lts/fermium
|
node-version: lts/gallium
|
||||||
- e2e-test:
|
- e2e-test:
|
||||||
name: e2e-full-nightly
|
name: e2e-full-nightly
|
||||||
node-version: lts/fermium
|
node-version: lts/gallium
|
||||||
suite: full
|
suite: full
|
||||||
triggers:
|
triggers:
|
||||||
- schedule:
|
- schedule:
|
||||||
|
12
.eslintrc.js
12
.eslintrc.js
@ -11,12 +11,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
"plugin:compat/recommended",
|
||||||
"plugin:vue/recommended",
|
"plugin:vue/recommended",
|
||||||
"plugin:you-dont-need-lodash-underscore/compatible"
|
"plugin:you-dont-need-lodash-underscore/compatible"
|
||||||
],
|
],
|
||||||
"parser": "vue-eslint-parser",
|
"parser": "vue-eslint-parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "babel-eslint",
|
"parser": "@babel/eslint-parser",
|
||||||
|
"requireConfigFile": false,
|
||||||
"allowImportExportEverywhere": true,
|
"allowImportExportEverywhere": true,
|
||||||
"ecmaVersion": 2015,
|
"ecmaVersion": 2015,
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
@ -35,7 +37,6 @@ module.exports = {
|
|||||||
"no-inner-declarations": "off",
|
"no-inner-declarations": "off",
|
||||||
"no-use-before-define": ["error", "nofunc"],
|
"no-use-before-define": ["error", "nofunc"],
|
||||||
"no-caller": "error",
|
"no-caller": "error",
|
||||||
"no-sequences": "error",
|
|
||||||
"no-irregular-whitespace": "error",
|
"no-irregular-whitespace": "error",
|
||||||
"no-new": "error",
|
"no-new": "error",
|
||||||
"no-shadow": "error",
|
"no-shadow": "error",
|
||||||
@ -239,13 +240,12 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
"vue/max-attributes-per-line": ["error", {
|
"vue/max-attributes-per-line": ["error", {
|
||||||
"singleline": 1,
|
"singleline": 1,
|
||||||
"multiline": {
|
"multiline": 1,
|
||||||
"max": 1,
|
|
||||||
"allowFirstLine": true
|
|
||||||
}
|
|
||||||
}],
|
}],
|
||||||
|
"vue/first-attribute-linebreak": "error",
|
||||||
"vue/multiline-html-element-content-newline": "off",
|
"vue/multiline-html-element-content-newline": "off",
|
||||||
"vue/singleline-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"
|
"vue/no-mutating-props": "off"
|
||||||
|
|
||||||
},
|
},
|
||||||
|
19
.github/ISSUE_TEMPLATE/bug_report.md
vendored
19
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -17,15 +17,6 @@ assignees: ''
|
|||||||
#### Expected vs Current Behavior
|
#### Expected vs Current Behavior
|
||||||
<!--- Tell us what should have happened -->
|
<!--- Tell us what should have happened -->
|
||||||
|
|
||||||
#### Impact Check List
|
|
||||||
<!--- Please select from the following options -->
|
|
||||||
|
|
||||||
- [ ] Data loss or misrepresented data?
|
|
||||||
- [ ] Regression? Did this used to work or has it always been broken?
|
|
||||||
- [ ] Is there a workaround available?
|
|
||||||
- [ ] Does this impact a critical component?
|
|
||||||
- [ ] Is this just a visual bug with no functional impact?
|
|
||||||
|
|
||||||
#### Steps to Reproduce
|
#### Steps to Reproduce
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
@ -35,10 +26,20 @@ assignees: ''
|
|||||||
4.
|
4.
|
||||||
|
|
||||||
#### Environment
|
#### Environment
|
||||||
|
<!--- If encountered on local machine, execute the following:
|
||||||
|
<!--- npx envinfo --system --browsers --npmPackages --binaries --languages --markdown -->
|
||||||
* Open MCT Version: <!--- date of build, version, or SHA -->
|
* Open MCT Version: <!--- date of build, version, or SHA -->
|
||||||
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
|
* Deployment Type: <!--- npm dev? VIPER Dev? openmct-yamcs? -->
|
||||||
* OS:
|
* OS:
|
||||||
* Browser:
|
* Browser:
|
||||||
|
|
||||||
|
#### Impact Check List
|
||||||
|
<!--- Please select from the following options -->
|
||||||
|
- [ ] Data loss or misrepresented data?
|
||||||
|
- [ ] Regression? Did this used to work or has it always been broken?
|
||||||
|
- [ ] Is there a workaround available?
|
||||||
|
- [ ] Does this impact a critical component?
|
||||||
|
- [ ] Is this just a visual bug with no functional impact?
|
||||||
|
|
||||||
#### Additional Information
|
#### Additional Information
|
||||||
<!--- Include any screenshots, gifs, or logs which will expedite triage -->
|
<!--- Include any screenshots, gifs, or logs which will expedite triage -->
|
||||||
|
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -11,6 +11,8 @@ updates:
|
|||||||
- "dependencies"
|
- "dependencies"
|
||||||
- "pr:e2e"
|
- "pr:e2e"
|
||||||
- "pr:daveit"
|
- "pr:daveit"
|
||||||
|
- "pr:visual"
|
||||||
|
- "pr:platform"
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
27
.github/workflows/e2e-pr.yml
vendored
27
.github/workflows/e2e-pr.yml
vendored
@ -2,15 +2,22 @@ name: "e2e-pr"
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [ labeled ]
|
types:
|
||||||
|
- labeled
|
||||||
|
- opened
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
e2e-full:
|
e2e-full:
|
||||||
if: ${{ github.event.label.name == 'pr:e2e' }}
|
if: ${{ github.event.label.name == 'pr:e2e' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Success
|
- name: Trigger Success
|
||||||
uses: actions/github-script@v5
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
@ -19,20 +26,20 @@ jobs:
|
|||||||
repo: "openmct",
|
repo: "openmct",
|
||||||
body: 'Started e2e Run. Follow along: https://github.com/nasa/openmct/actions/runs/' + context.runId
|
body: 'Started e2e Run. Follow along: https://github.com/nasa/openmct/actions/runs/' + context.runId
|
||||||
})
|
})
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '16'
|
||||||
- run: npx playwright install-deps
|
- run: npx playwright@1.19.2 install
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run test:e2e:full
|
- run: npm run test:e2e:full
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: test-results
|
path: test-results
|
||||||
- name: Test success
|
- name: Test success
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: actions/github-script@v5
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
@ -43,7 +50,7 @@ jobs:
|
|||||||
})
|
})
|
||||||
- name: Test failure
|
- name: Test failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: actions/github-script@v5
|
uses: actions/github-script@v6
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.createComment({
|
github.rest.issues.createComment({
|
||||||
|
9
.github/workflows/e2e-visual.yml
vendored
9
.github/workflows/e2e-visual.yml
vendored
@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
- labeled
|
- labeled
|
||||||
|
- opened
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '28 21 * * 1-5'
|
- cron: '28 21 * * 1-5'
|
||||||
|
|
||||||
@ -12,11 +13,11 @@ jobs:
|
|||||||
if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }}
|
if: ${{ github.event.label.name == 'pr:visual' }} || ${{ github.event.workflow_dispatch }} || ${{ github.event.schedule }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '16'
|
||||||
- run: npx playwright install-deps
|
- run: npx playwright@1.19.2 install
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- name: Run the e2e visual tests
|
- name: Run the e2e visual tests
|
||||||
run: npm run test:e2e:visual
|
run: npm run test:e2e:visual
|
||||||
|
6
.github/workflows/e2e.yml
vendored
6
.github/workflows/e2e.yml
vendored
@ -10,12 +10,12 @@ jobs:
|
|||||||
e2e:
|
e2e:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.version }}
|
ref: ${{ github.event.inputs.version }}
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '16'
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- name: Run the e2e tests
|
- name: Run the e2e tests
|
||||||
run: npm run test:e2e:ci
|
run: npm run test:e2e:ci
|
||||||
|
90
.github/workflows/lighthouse.yml
vendored
90
.github/workflows/lighthouse.yml
vendored
@ -6,15 +6,93 @@ on:
|
|||||||
description: 'Which branch do you want to test?' # Limited to branch for now
|
description: 'Which branch do you want to test?' # Limited to branch for now
|
||||||
required: false
|
required: false
|
||||||
default: 'master'
|
default: 'master'
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- labeled
|
||||||
jobs:
|
jobs:
|
||||||
lighthouse:
|
lighthouse-pr:
|
||||||
|
if: ${{ github.event.label.name == 'pr:lighthouse' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- 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
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.version }}
|
ref: ${{ github.event.inputs.version }}
|
||||||
- uses: actions/setup-node@v2
|
- name: Install Node 14
|
||||||
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '14'
|
node-version: '16'
|
||||||
- run: npm install && npm install -g @lhci/cli #Don't want to include this in our deps
|
- name: Cache node modules
|
||||||
- run: lhci autorun
|
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
|
||||||
|
|
12
.github/workflows/npm-prerelease.yml
vendored
12
.github/workflows/npm-prerelease.yml
vendored
@ -11,10 +11,10 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 16
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
@ -22,10 +22,10 @@ jobs:
|
|||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 16
|
||||||
registry-url: https://registry.npmjs.org/
|
registry-url: https://registry.npmjs.org/
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm publish --access public --tag unstable
|
- run: npm publish --access public --tag unstable
|
||||||
|
34
.github/workflows/pr-platform.yml
vendored
Normal file
34
.github/workflows/pr-platform.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: "pr-platform"
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
types: [ labeled ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-full:
|
||||||
|
if: ${{ github.event.label.name == 'pr:platform' }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- macos-latest
|
||||||
|
- windows-latest
|
||||||
|
node_version:
|
||||||
|
- 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
|
7
.github/workflows/prcop.yml
vendored
7
.github/workflows/prcop.yml
vendored
@ -1,12 +1,15 @@
|
|||||||
name: PRcop
|
name: PRCop
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
- edited
|
- edited
|
||||||
- synchronize
|
- synchronize
|
||||||
- ready_for_review
|
- ready_for_review
|
||||||
- review_requested
|
- review_requested
|
||||||
|
- review_request_removed
|
||||||
pull_request_review_comment:
|
pull_request_review_comment:
|
||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
@ -14,7 +17,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
prcop:
|
prcop:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: PRcop
|
name: Template Check
|
||||||
steps:
|
steps:
|
||||||
- name: Linting Pull Request
|
- name: Linting Pull Request
|
||||||
uses: makaroni4/prcop@v1.0.35
|
uses: makaroni4/prcop@v1.0.35
|
||||||
|
61
.npmignore
61
.npmignore
@ -1,44 +1,27 @@
|
|||||||
*.scssc
|
# Ignore everything first (will not ignore special files like LICENSE.md,
|
||||||
*.zip
|
# README.md, and package.json)...
|
||||||
*.gzip
|
/**/*
|
||||||
*.tgz
|
|
||||||
*.DS_Store
|
|
||||||
|
|
||||||
*.sass-cache
|
# ...but include these folders...
|
||||||
*COMPILE.css
|
!/dist/**/*
|
||||||
|
!/src/**/*
|
||||||
|
|
||||||
# Intellij project configuration files
|
# We might be able to remove this if it is not imported by any project directly.
|
||||||
*.idea
|
# https://github.com/nasa/openmct/issues/4992
|
||||||
*.iml
|
!/example/**/*
|
||||||
|
|
||||||
# External dependencies
|
# We will remove this in https://github.com/nasa/openmct/issues/4922
|
||||||
|
!/app.js
|
||||||
|
|
||||||
# Build output
|
# ...except for these files in the above folders.
|
||||||
target
|
/src/**/*Spec.js
|
||||||
|
/src/**/test/
|
||||||
|
# TODO move test utils into test/ folders
|
||||||
|
/src/utils/testing.js
|
||||||
|
|
||||||
# Mac OS X Finder
|
# Also include these special top-level files.
|
||||||
.DS_Store
|
!copyright-notice.js
|
||||||
|
!copyright-notice.html
|
||||||
# Closed source libraries
|
!index.html
|
||||||
closed-lib
|
!openmct.js
|
||||||
|
!SECURITY.md
|
||||||
# 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,9 +1,4 @@
|
|||||||
loglevel=warn
|
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
|
#Prevent folks from ignoring an important error when building from source
|
||||||
engine-strict=true
|
engine-strict=true
|
@ -65,6 +65,12 @@ Open MCT is built using [`npm`](http://npmjs.com/) and [`webpack`](https://webpa
|
|||||||
|
|
||||||
See our documentation for a guide on [building Applications with Open MCT](https://github.com/nasa/openmct/blob/master/API.md#starting-an-open-mct-application).
|
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
|
## Plugins
|
||||||
|
|
||||||
Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group
|
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,
|
compiler,
|
||||||
{
|
{
|
||||||
publicPath: '/dist',
|
publicPath: '/dist',
|
||||||
logLevel: 'warn'
|
stats: 'errors-warnings'
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
|
9
babel.coverage.js
Normal file
9
babel.coverage.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// This is a Babel config that webpack.coverage.js uses in order to instrument
|
||||||
|
// code with coverage instrumentation.
|
||||||
|
const babelConfig = {
|
||||||
|
plugins: [['babel-plugin-istanbul', {
|
||||||
|
extension: ['.js', '.vue']
|
||||||
|
}]]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = babelConfig;
|
62
e2e/tests/branding.e2e.spec.js
Normal file
62
e2e/tests/branding.e2e.spec.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests which verify branding related components.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Branding tests', () => {
|
||||||
|
test('About Modal launches with basic branding properties', async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click About button
|
||||||
|
await page.click('.l-shell__app-logo');
|
||||||
|
|
||||||
|
// Verify that the NASA Logo Appears
|
||||||
|
await expect(await page.locator('.c-about__image')).toBeVisible();
|
||||||
|
|
||||||
|
// Modify the Build information in 'about' Modal
|
||||||
|
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
|
||||||
|
await expect(versionInformationLocator).toBeEnabled();
|
||||||
|
await expect.soft(versionInformationLocator).toContainText(/Version: \d/);
|
||||||
|
await expect.soft(versionInformationLocator).toContainText(/Build Date: ((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun))/);
|
||||||
|
await expect.soft(versionInformationLocator).toContainText(/Revision: \b[0-9a-f]{5,40}\b/);
|
||||||
|
await expect.soft(versionInformationLocator).toContainText(/Branch: ./);
|
||||||
|
});
|
||||||
|
test('Verify Links in About Modal', async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click About button
|
||||||
|
await page.click('.l-shell__app-logo');
|
||||||
|
|
||||||
|
// Verify that clicking on the third party licenses information opens up another tab on licenses url
|
||||||
|
const [page2] = await Promise.all([
|
||||||
|
page.waitForEvent('popup'),
|
||||||
|
page.locator('text=click here for third party licensing information').click()
|
||||||
|
]);
|
||||||
|
expect(page2.waitForURL('**\/licenses**')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
62
e2e/tests/example/eventGenerator.e2e.spec.js
Normal file
62
e2e/tests/example/eventGenerator.e2e.spec.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests which verify the basic operations surrounding the example event generator.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Example Event Generator Operations', () => {
|
||||||
|
test('Can create example event generator with a name', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
// let's make an event generator
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
// Click li:has-text("Event Message Generator")
|
||||||
|
await page.locator('li:has-text("Event Message Generator")').click();
|
||||||
|
// Click text=Properties Title Notes >> input[type="text"]
|
||||||
|
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||||
|
// Fill text=Properties Title Notes >> input[type="text"]
|
||||||
|
await page.locator('text=Properties Title Notes >> input[type="text"]').fill('Test Event Generator');
|
||||||
|
// Press Enter
|
||||||
|
await page.locator('text=Properties Title Notes >> input[type="text"]').press('Enter');
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({ url: /.*&view=table/ }),
|
||||||
|
page.locator('text=OK').click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Test Event Generator');
|
||||||
|
// Click button:has-text("Fixed Timespan")
|
||||||
|
await page.locator('button:has-text("Fixed Timespan")').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.fixme('telemetry is coming in for test event', async ({ page }) => {
|
||||||
|
// Go to object created in step one
|
||||||
|
// Verify the telemetry table is filled with > 1 row
|
||||||
|
});
|
||||||
|
test.fixme('telemetry is sorted by time ascending', async ({ page }) => {
|
||||||
|
// Go to object created in step one
|
||||||
|
// Verify the telemetry table has a class with "is-sorting asc"
|
||||||
|
});
|
||||||
|
});
|
166
e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js
Normal file
166
e2e/tests/example/generator/SinewaveLimitProvider.e2e.spec.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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]+/);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -19,32 +19,24 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import {
|
|
||||||
createOpenMct,
|
|
||||||
resetApplicationState
|
|
||||||
} from 'utils/testing';
|
|
||||||
|
|
||||||
describe("the plugin", () => {
|
/*
|
||||||
const NON_EDITABLE_FOLDER_KEY = 'noneditable.folder';
|
This test suite is dedicated to tests which verify the basic operations surrounding moving objects.
|
||||||
let openmct;
|
*/
|
||||||
|
|
||||||
beforeEach((done) => {
|
const { test, expect } = require('@playwright/test');
|
||||||
openmct = createOpenMct();
|
|
||||||
openmct.install(openmct.plugins.NonEditableFolder());
|
|
||||||
|
|
||||||
openmct.on('start', done);
|
test.describe('Move item tests', () => {
|
||||||
openmct.startHeadless();
|
test.fixme('Create a basic object and verify that it can be moved to another Folder', async ({ page }) => {
|
||||||
|
//Create and save Folder
|
||||||
|
//Create and save Domain Object
|
||||||
|
//Verify that the newly created domain object can be moved to Folder from Step 1.
|
||||||
|
//Verify that newly moved object appears in the correct point in Tree
|
||||||
|
//Verify that newly moved object appears correctly in Inspector panel
|
||||||
});
|
});
|
||||||
|
test.fixme('Create a basic object and verify that it cannot be moved to object without Composition Provider', async ({ page }) => {
|
||||||
afterEach(() => {
|
//Create and save Telemetry Object
|
||||||
return resetApplicationState(openmct);
|
//Create and save Domain Object
|
||||||
|
//Verify that the newly created domain object cannot be moved to Telemetry Object from step 1.
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds the new non-editable folder type', () => {
|
|
||||||
const type = openmct.types.get(NON_EDITABLE_FOLDER_KEY);
|
|
||||||
|
|
||||||
expect(type).toBeDefined();
|
|
||||||
expect(type.definition.creatable).toBeFalse();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
27
e2e/tests/persistence/addNoneditableObject.js
Normal file
27
e2e/tests/persistence/addNoneditableObject.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(function () {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const PERSISTENCE_KEY = 'persistence-tests';
|
||||||
|
const openmct = window.openmct;
|
||||||
|
|
||||||
|
openmct.objects.addRoot({
|
||||||
|
namespace: PERSISTENCE_KEY,
|
||||||
|
key: PERSISTENCE_KEY
|
||||||
|
});
|
||||||
|
|
||||||
|
openmct.objects.addProvider(PERSISTENCE_KEY, {
|
||||||
|
get(identifier) {
|
||||||
|
if (identifier.key !== PERSISTENCE_KEY) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
return Promise.resolve({
|
||||||
|
identifier,
|
||||||
|
type: 'folder',
|
||||||
|
name: 'Persistence Testing',
|
||||||
|
location: 'ROOT',
|
||||||
|
composition: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}());
|
77
e2e/tests/persistence/persistability.e2e.spec.js
Normal file
77
e2e/tests/persistence/persistability.e2e.spec.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests which verify the basic operations surrounding conditionSets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// https://github.com/nasa/openmct/issues/4323#issuecomment-1067282651
|
||||||
|
|
||||||
|
test.describe('Persistence operations', () => {
|
||||||
|
// add non persistable root item
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
await page.addInitScript({ path: path.join(__dirname, 'addNoneditableObject.js') });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Persistability should be respected in the create form location field', async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
// Click text=Condition Set
|
||||||
|
await page.click('text=Condition Set');
|
||||||
|
|
||||||
|
// Click form[name="mctForm"] >> text=Persistence Testing
|
||||||
|
await page.locator('form[name="mctForm"] >> text=Persistence Testing').click();
|
||||||
|
|
||||||
|
// Check that "OK" button is disabled
|
||||||
|
const okButton = page.locator('button:has-text("OK")');
|
||||||
|
await expect(okButton).toBeDisabled();
|
||||||
|
});
|
||||||
|
test('Non-persistable objects should not show persistence related actions', async ({ page }) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click text=Persistence Testing >> nth=0
|
||||||
|
await page.locator('text=Persistence Testing').first().click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuOptions = page.locator('.c-menu ul');
|
||||||
|
|
||||||
|
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
|
||||||
|
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);
|
||||||
|
});
|
||||||
|
test.fixme('Cannot move a previously created domain object to non-peristable boject in Move Modal', async ({ page }) => {
|
||||||
|
//Create a domain object
|
||||||
|
//Save Domain object
|
||||||
|
//Move Object and verify that cannot select non-persistable object
|
||||||
|
//Move Object to My Items
|
||||||
|
//Verify successful move
|
||||||
|
});
|
||||||
|
});
|
48
e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js
Normal file
48
e2e/tests/plugins/ExportAsJSON/exportAsJson.e2e.spec.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to tests which verify the basic operations surrounding exportAsJSON.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('ExportAsJSON', () => {
|
||||||
|
test.fixme('Create a basic object and verify that it can be exported as JSON from Tree', async ({ page }) => {
|
||||||
|
//Create domain object
|
||||||
|
//Save Domain Object
|
||||||
|
//Verify that the newly created domain object can be exported as JSON from the Tree
|
||||||
|
});
|
||||||
|
test.fixme('Create a basic object and verify that it can be exported as JSON from 3 dot menu', async ({ page }) => {
|
||||||
|
//Create domain object
|
||||||
|
//Save Domain Object
|
||||||
|
//Verify that the newly created domain object can be exported as JSON from the 3 dot menu
|
||||||
|
});
|
||||||
|
test.fixme('Verify that a nested Object can be exported as JSON', async ({ page }) => {
|
||||||
|
// Create 2 objects with hierarchy
|
||||||
|
// Export as JSON
|
||||||
|
// Verify Hiearchy
|
||||||
|
});
|
||||||
|
test.fixme('Verify that the ExportAsJSON dropdown does not appear for the item X', async ({ page }) => {
|
||||||
|
// Other than non-persistible objects
|
||||||
|
});
|
||||||
|
});
|
46
e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js
Normal file
46
e2e/tests/plugins/ImportAsJSON/importAsJson.e2e.spec.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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
|
||||||
|
});
|
||||||
|
});
|
66
e2e/tests/plugins/clock/Clock.e2e.spec.js
Normal file
66
e2e/tests/plugins/clock/Clock.e2e.spec.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -26,8 +26,8 @@ This test suite is dedicated to tests which verify the basic operations surround
|
|||||||
|
|
||||||
const { test, expect } = require('@playwright/test');
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('condition set', () => {
|
test.describe('Condition Set Operations', () => {
|
||||||
test('create new button `condition set` creates new condition object', async ({ page }) => {
|
test('Create new button `condition set` creates new condition object', async ({ page }) => {
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
@ -45,4 +45,21 @@ test.describe('condition set', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
});
|
});
|
||||||
|
test.fixme('condition set object properties exist', async ({ page }) => {
|
||||||
|
//Go to object created in step one
|
||||||
|
//Verify the Condition Set properties persist on Save
|
||||||
|
//Verify the Condition Set properties persist on page.reload()
|
||||||
|
});
|
||||||
|
test.fixme('condition set object can be modified', async ({ page }) => {
|
||||||
|
//Go to object created in step one
|
||||||
|
//Update the Condition Set properties
|
||||||
|
//Verify the Condition Set properties persist on Save
|
||||||
|
//Verify the Condition Set properties persist on page.reload()
|
||||||
|
});
|
||||||
|
test.fixme('condition set object can be deleted', async ({ page }) => {
|
||||||
|
//Go to object created in step one
|
||||||
|
//Verify that Condition Set object can be deleted
|
||||||
|
//Verify the Condition Set object does not exist in Tree
|
||||||
|
//Verify the Condition Set object does not exist with direct navigation to object's URL
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
217
e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js
Normal file
217
e2e/tests/plugins/imagery/exampleImagery.e2e.spec.js
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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');
|
||||||
|
});
|
190
e2e/tests/plugins/plot/autoscale.e2e.spec.js
Normal file
190
e2e/tests/plugins/plot/autoscale.e2e.spec.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
279
e2e/tests/plugins/plot/log-plot.e2e.spec.js
Normal file
279
e2e/tests/plugins/plot/log-plot.e2e.spec.js
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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);
|
||||||
|
}
|
69
e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js
Normal file
69
e2e/tests/plugins/timeConductor/timeConductor.e2e.spec.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Time counductor operations', () => {
|
||||||
|
test('validate start time does not exceeds end time', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
|
let startDate = 'xxxx-01-01 01:00:00.000Z';
|
||||||
|
startDate = year + startDate.substring(4);
|
||||||
|
|
||||||
|
let endDate = 'xxxx-01-01 02:00:00.000Z';
|
||||||
|
endDate = year + endDate.substring(4);
|
||||||
|
|
||||||
|
const startTimeLocator = page.locator('input[type="text"]').first();
|
||||||
|
const endTimeLocator = page.locator('input[type="text"]').nth(1);
|
||||||
|
|
||||||
|
// Click start time
|
||||||
|
await startTimeLocator.click();
|
||||||
|
|
||||||
|
// Click end time
|
||||||
|
await endTimeLocator.click();
|
||||||
|
|
||||||
|
await endTimeLocator.fill(endDate.toString());
|
||||||
|
await startTimeLocator.fill(startDate.toString());
|
||||||
|
|
||||||
|
// invalid start date
|
||||||
|
startDate = (year + 1) + startDate.substring(4);
|
||||||
|
await startTimeLocator.fill(startDate.toString());
|
||||||
|
await endTimeLocator.click();
|
||||||
|
|
||||||
|
const startDateValidityStatus = await startTimeLocator.evaluate((element) => element.checkValidity());
|
||||||
|
expect(startDateValidityStatus).not.toBeTruthy();
|
||||||
|
|
||||||
|
// fix to valid start date
|
||||||
|
startDate = (year - 1) + startDate.substring(4);
|
||||||
|
await startTimeLocator.fill(startDate.toString());
|
||||||
|
|
||||||
|
// invalid end date
|
||||||
|
endDate = (year - 2) + endDate.substring(4);
|
||||||
|
await endTimeLocator.fill(endDate.toString());
|
||||||
|
await startTimeLocator.click();
|
||||||
|
|
||||||
|
const endDateValidityStatus = await endTimeLocator.evaluate((element) => element.checkValidity());
|
||||||
|
expect(endDateValidityStatus).not.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -111,3 +111,63 @@ test('Visual - Default Condition Widget', async ({ page }) => {
|
|||||||
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
await percySnapshot(page, 'Default Condition Widget');
|
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) {
|
generateData(firstObservedTime, count, startTime, duration, name) {
|
||||||
const millisecondsSinceStart = startTime - firstObservedTime;
|
const millisecondsSinceStart = startTime - firstObservedTime;
|
||||||
const utc = Math.floor(startTime / duration) * duration;
|
const utc = startTime + (count * duration);
|
||||||
const ind = count % messages.length;
|
const ind = count % messages.length;
|
||||||
const message = messages[ind] + " - [" + millisecondsSinceStart + "]";
|
const message = messages[ind] + " - [" + millisecondsSinceStart + "]";
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ class EventTelemetryProvider {
|
|||||||
const duration = domainObject.telemetry.duration * 1000;
|
const duration = domainObject.telemetry.duration * 1000;
|
||||||
const size = options.size ? options.size : this.defaultSize;
|
const size = options.size ? options.size : this.defaultSize;
|
||||||
const data = [];
|
const data = [];
|
||||||
const firstObservedTime = Date.now();
|
const firstObservedTime = options.start;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
if (options.strategy === 'latest' || options.size === 1) {
|
if (options.strategy === 'latest' || options.size === 1) {
|
||||||
@ -83,7 +83,7 @@ class EventTelemetryProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (start <= end && data.length < size) {
|
while (start <= end && data.length < size) {
|
||||||
const startTime = Date.now() + count;
|
const startTime = options.start + count;
|
||||||
data.push(this.generateData(firstObservedTime, count, startTime, duration, domainObject.name));
|
data.push(this.generateData(firstObservedTime, count, startTime, duration, domainObject.name));
|
||||||
start += duration;
|
start += duration;
|
||||||
count += 1;
|
count += 1;
|
||||||
|
@ -35,6 +35,7 @@ describe('the plugin', () => {
|
|||||||
telemetry: {
|
telemetry: {
|
||||||
duration: 0
|
duration: 0
|
||||||
},
|
},
|
||||||
|
options: {},
|
||||||
type: 'eventGenerator'
|
type: 'eventGenerator'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -61,7 +62,13 @@ describe('the plugin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("supports requests", async () => {
|
it("supports requests without start/end defined", async () => {
|
||||||
|
const telemetry = await openmct.telemetry.request(mockDomainObject);
|
||||||
|
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports requests with arbitrary start time in the past", async () => {
|
||||||
|
mockDomainObject.options.start = 100000000000; // Mar 03 1973
|
||||||
const telemetry = await openmct.telemetry.request(mockDomainObject);
|
const telemetry = await openmct.telemetry.request(mockDomainObject);
|
||||||
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
|
expect(telemetry[0].message).toContain('CC: Eagle, Houston');
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ import {
|
|||||||
} from '../../src/utils/testing';
|
} from '../../src/utils/testing';
|
||||||
import ExampleUserProvider from './ExampleUserProvider';
|
import ExampleUserProvider from './ExampleUserProvider';
|
||||||
|
|
||||||
describe("The Example User Plugin", () => {
|
xdescribe("The Example User Plugin", () => {
|
||||||
let openmct;
|
let openmct;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -35,8 +35,8 @@ define([
|
|||||||
phase: 0
|
phase: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
function GeneratorProvider() {
|
function GeneratorProvider(openmct) {
|
||||||
this.workerInterface = new WorkerInterface();
|
this.workerInterface = new WorkerInterface(openmct);
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||||
|
@ -21,20 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'raw-loader!./generatorWorker.js',
|
|
||||||
'uuid'
|
'uuid'
|
||||||
], function (
|
], function (
|
||||||
workerText,
|
|
||||||
uuid
|
uuid
|
||||||
) {
|
) {
|
||||||
|
function WorkerInterface(openmct) {
|
||||||
var workerBlob = new Blob(
|
// eslint-disable-next-line no-undef
|
||||||
[workerText],
|
const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`;
|
||||||
{type: 'application/javascript'}
|
|
||||||
);
|
|
||||||
var workerUrl = URL.createObjectURL(workerBlob);
|
|
||||||
|
|
||||||
function WorkerInterface() {
|
|
||||||
this.worker = new Worker(workerUrl);
|
this.worker = new Worker(workerUrl);
|
||||||
this.worker.onmessage = this.onMessage.bind(this);
|
this.worker.onmessage = this.onMessage.bind(this);
|
||||||
this.callbacks = {};
|
this.callbacks = {};
|
||||||
|
@ -146,7 +146,7 @@ define([
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.telemetry.addProvider(new GeneratorProvider());
|
openmct.telemetry.addProvider(new GeneratorProvider(openmct));
|
||||||
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
|
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
|
||||||
openmct.telemetry.addProvider(new SinewaveLimitProvider());
|
openmct.telemetry.addProvider(new SinewaveLimitProvider());
|
||||||
};
|
};
|
||||||
|
@ -190,11 +190,12 @@
|
|||||||
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
|
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
|
||||||
openmct.install(openmct.plugins.ObjectMigration());
|
openmct.install(openmct.plugins.ObjectMigration());
|
||||||
openmct.install(openmct.plugins.ClearData(
|
openmct.install(openmct.plugins.ClearData(
|
||||||
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
|
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'example.imagery'],
|
||||||
{indicator: true}
|
{indicator: true}
|
||||||
));
|
));
|
||||||
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
|
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
|
||||||
openmct.install(openmct.plugins.Timer());
|
openmct.install(openmct.plugins.Timer());
|
||||||
|
openmct.install(openmct.plugins.Timelist());
|
||||||
openmct.start();
|
openmct.start();
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
const testsContext = require.context('.', true, /\/(src|platform|\.\/example)\/.*Spec.js$/);
|
const testsContext = require.context('.', true, /^\.\/(src|example)\/.*Spec.js$/);
|
||||||
|
|
||||||
testsContext.keys().forEach(testsContext);
|
testsContext.keys().forEach(testsContext);
|
||||||
|
@ -22,29 +22,9 @@
|
|||||||
|
|
||||||
/*global module,process*/
|
/*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) => {
|
module.exports = (config) => {
|
||||||
const webpackConfig = require('./webpack.dev.js');
|
const webpackConfig = require('./webpack.coverage.js');
|
||||||
delete webpackConfig.output;
|
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({
|
config.set({
|
||||||
basePath: '',
|
basePath: '',
|
||||||
@ -58,11 +38,15 @@ module.exports = (config) => {
|
|||||||
{
|
{
|
||||||
pattern: 'dist/inMemorySearchWorker.js*',
|
pattern: 'dist/inMemorySearchWorker.js*',
|
||||||
included: false
|
included: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: 'dist/generatorWorker.js*',
|
||||||
|
included: false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
port: 9876,
|
port: 9876,
|
||||||
reporters: reporters,
|
reporters: ['spec', 'junit', 'coverage-istanbul'],
|
||||||
browsers: browsers,
|
browsers: [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'],
|
||||||
client: {
|
client: {
|
||||||
jasmine: {
|
jasmine: {
|
||||||
random: false,
|
random: false,
|
||||||
@ -83,12 +67,6 @@ module.exports = (config) => {
|
|||||||
colors: true,
|
colors: true,
|
||||||
logLevel: config.LOG_INFO,
|
logLevel: config.LOG_INFO,
|
||||||
autoWatch: true,
|
autoWatch: true,
|
||||||
// HTML test reporting.
|
|
||||||
// htmlReporter: {
|
|
||||||
// outputDir: "dist/reports/tests",
|
|
||||||
// preserveDescribeNesting: true,
|
|
||||||
// foldAll: false
|
|
||||||
// },
|
|
||||||
junitReporter: {
|
junitReporter: {
|
||||||
outputDir: "dist/reports/tests",
|
outputDir: "dist/reports/tests",
|
||||||
outputFile: "test-results.xml",
|
outputFile: "test-results.xml",
|
||||||
@ -96,9 +74,7 @@ module.exports = (config) => {
|
|||||||
},
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
fixWebpackSourcePaths: true,
|
fixWebpackSourcePaths: true,
|
||||||
dir: process.env.CIRCLE_ARTIFACTS
|
dir: "dist/reports/coverage",
|
||||||
? process.env.CIRCLE_ARTIFACTS + '/coverage'
|
|
||||||
: "dist/reports/coverage",
|
|
||||||
reports: ['lcovonly', 'text-summary'],
|
reports: ['lcovonly', 'text-summary'],
|
||||||
thresholds: {
|
thresholds: {
|
||||||
global: {
|
global: {
|
||||||
@ -120,8 +96,7 @@ module.exports = (config) => {
|
|||||||
},
|
},
|
||||||
webpack: webpackConfig,
|
webpack: webpackConfig,
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
stats: 'errors-only',
|
stats: 'errors-warnings'
|
||||||
logLevel: 'warn'
|
|
||||||
},
|
},
|
||||||
concurrency: 1,
|
concurrency: 1,
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
|
119
package.json
119
package.json
@ -1,40 +1,44 @@
|
|||||||
{
|
{
|
||||||
"name": "openmct",
|
"name": "openmct",
|
||||||
"version": "1.8.5-SNAPSHOT",
|
"version": "2.0.3",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@braintree/sanitize-url": "5.0.2",
|
"@babel/eslint-parser": "7.16.3",
|
||||||
"@percy/cli": "1.0.0-beta.73",
|
"@braintree/sanitize-url": "6.0.0",
|
||||||
"@percy/playwright": "1.0.1",
|
"@percy/cli": "1.0.4",
|
||||||
"@playwright/test": "1.18.1",
|
"@percy/playwright": "1.0.2",
|
||||||
"allure-playwright": "2.0.0-beta.14",
|
"@playwright/test": "1.19.2",
|
||||||
"babel-eslint": "10.1.0",
|
"@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",
|
||||||
|
"allure-playwright": "2.0.0-beta.15",
|
||||||
|
"babel-loader": "8.2.3",
|
||||||
|
"babel-plugin-istanbul": "6.1.1",
|
||||||
"comma-separated-values": "3.6.4",
|
"comma-separated-values": "3.6.4",
|
||||||
"copy-webpack-plugin": "10.2.0",
|
"copy-webpack-plugin": "10.2.0",
|
||||||
"core-js": "3.20.3",
|
"cross-env": "7.0.3",
|
||||||
"cross-env": "6.0.3",
|
|
||||||
"css-loader": "4.0.0",
|
"css-loader": "4.0.0",
|
||||||
"d3-axis": "1.0.x",
|
"d3-axis": "1.0.x",
|
||||||
"d3-scale": "1.0.x",
|
"d3-scale": "1.0.x",
|
||||||
"d3-selection": "1.3.x",
|
"d3-selection": "1.3.x",
|
||||||
"eslint": "7.0.0",
|
"eslint": "8.13.0",
|
||||||
|
"eslint-plugin-compat": "4.0.2",
|
||||||
"eslint-plugin-playwright": "0.8.0",
|
"eslint-plugin-playwright": "0.8.0",
|
||||||
"eslint-plugin-vue": "7.5.0",
|
"eslint-plugin-vue": "8.5.0",
|
||||||
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
"eslint-plugin-you-dont-need-lodash-underscore": "6.12.0",
|
||||||
"eventemitter3": "1.2.0",
|
"eventemitter3": "1.2.0",
|
||||||
"exports-loader": "0.7.0",
|
"exports-loader": "0.7.0",
|
||||||
"express": "4.13.1",
|
"express": "4.13.1",
|
||||||
"file-loader": "6.1.0",
|
"file-saver": "2.0.5",
|
||||||
"file-saver": "1.3.8",
|
"git-rev-sync": "3.0.2",
|
||||||
"git-rev-sync": "1.4.0",
|
|
||||||
"html-loader": "0.5.5",
|
|
||||||
"html2canvas": "1.4.1",
|
"html2canvas": "1.4.1",
|
||||||
"imports-loader": "0.8.0",
|
"imports-loader": "0.8.0",
|
||||||
"istanbul-instrumenter-loader": "^3.0.1",
|
"jasmine-core": "4.0.1",
|
||||||
"jasmine-core": "4.0.0",
|
|
||||||
"jsdoc": "3.5.5",
|
"jsdoc": "3.5.5",
|
||||||
"karma": "6.3.15",
|
"karma": "6.3.18",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.1",
|
||||||
"karma-cli": "2.0.0",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "2.1.1",
|
"karma-coverage": "2.1.1",
|
||||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||||
@ -42,54 +46,55 @@
|
|||||||
"karma-jasmine": "4.0.1",
|
"karma-jasmine": "4.0.1",
|
||||||
"karma-junit-reporter": "2.0.1",
|
"karma-junit-reporter": "2.0.1",
|
||||||
"karma-sourcemap-loader": "0.3.8",
|
"karma-sourcemap-loader": "0.3.8",
|
||||||
"karma-spec-reporter": "0.0.33",
|
"karma-spec-reporter": "0.0.34",
|
||||||
"karma-webpack": "^5.0.0",
|
"karma-webpack": "5.0.0",
|
||||||
"location-bar": "^3.0.1",
|
"lighthouse": "9.5.0",
|
||||||
"lodash": "^4.17.12",
|
"location-bar": "3.0.1",
|
||||||
"mini-css-extract-plugin": "2.4.5",
|
"lodash": "4.17.21",
|
||||||
"moment": "2.25.3",
|
"mini-css-extract-plugin": "2.6.0",
|
||||||
"moment-duration-format": "^2.2.2",
|
"moment": "2.29.1",
|
||||||
"moment-timezone": "0.5.28",
|
"moment-duration-format": "2.3.2",
|
||||||
"node-bourbon": "^4.2.3",
|
"moment-timezone": "0.5.34",
|
||||||
"painterro": "^1.2.56",
|
"node-bourbon": "4.2.3",
|
||||||
"playwright": "^1.18.1",
|
"painterro": "1.2.56",
|
||||||
"plotly.js-basic-dist": "^2.5.0",
|
"plotly.js-basic-dist": "2.5.0",
|
||||||
"plotly.js-gl2d-dist": "^2.5.0",
|
"plotly.js-gl2d-dist": "2.5.0",
|
||||||
"printj": "^1.2.1",
|
"printj": "1.3.1",
|
||||||
"raw-loader": "^0.5.1",
|
"request": "2.88.2",
|
||||||
"request": "^2.69.0",
|
"resolve-url-loader": "5.0.0",
|
||||||
"resolve-url-loader": "4.0.0",
|
"sass": "1.49.9",
|
||||||
"sass": "1.49.0",
|
"sass-loader": "12.6.0",
|
||||||
"sass-loader": "12.4.0",
|
|
||||||
"sinon": "13.0.1",
|
"sinon": "13.0.1",
|
||||||
"style-loader": "^1.0.1",
|
"style-loader": "^1.0.1",
|
||||||
"uuid": "^3.3.3",
|
"uuid": "3.3.3",
|
||||||
"vue": "2.5.6",
|
"vue": "2.6.14",
|
||||||
"vue-eslint-parser": "8.2.0",
|
"vue-eslint-parser": "8.3.0",
|
||||||
"vue-loader": "15.9.8",
|
"vue-loader": "15.9.8",
|
||||||
"vue-template-compiler": "2.5.6",
|
"vue-template-compiler": "2.6.14",
|
||||||
"webpack": "5.67.0",
|
"webpack": "5.68.0",
|
||||||
"webpack-cli": "4.9.2",
|
"webpack-cli": "4.9.2",
|
||||||
"webpack-dev-middleware": "^3.1.3",
|
"webpack-dev-middleware": "5.3.1",
|
||||||
"webpack-hot-middleware": "^2.22.3",
|
"webpack-hot-middleware": "2.25.1",
|
||||||
"webpack-merge": "5.8.0",
|
"webpack-merge": "5.8.0",
|
||||||
"zepto": "^1.2.0"
|
"zepto": "1.2.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf ./dist ./node_modules; rm package-lock.json",
|
"clean": "rm -rf ./dist ./node_modules ./package-lock.json",
|
||||||
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
|
"clean-test-lint": "npm run clean; npm install; npm run test; npm run lint",
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
"lint": "eslint example src --ext .js,.vue openmct.js",
|
"lint": "eslint example src --ext .js,.vue openmct.js",
|
||||||
"lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
|
"lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
|
||||||
"build:prod": "cross-env webpack --config webpack.prod.js",
|
"build:prod": "cross-env webpack --config webpack.prod.js",
|
||||||
"build:dev": "webpack --config webpack.dev.js",
|
"build:dev": "webpack --config webpack.dev.js",
|
||||||
|
"build:coverage": "webpack --config webpack.coverage.js",
|
||||||
"build:watch": "webpack --config webpack.dev.js --watch",
|
"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": "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:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
||||||
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
|
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor",
|
||||||
"test:coverage:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
|
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
||||||
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition.e2e",
|
"test:e2e:debug": "npm run test:e2e:local -- --debug",
|
||||||
"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: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: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",
|
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||||
@ -105,8 +110,18 @@
|
|||||||
"url": "https://github.com/nasa/openmct.git"
|
"url": "https://github.com/nasa/openmct.git"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.20.1 <15.0.0"
|
"node": ">=14.19.1"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"core-js": "3.21.1"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"Firefox ESR",
|
||||||
|
"not IE 11",
|
||||||
|
"last 2 Chrome versions",
|
||||||
|
"unreleased Chrome versions",
|
||||||
|
"ios_saf > 15"
|
||||||
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true
|
"private": true
|
||||||
|
@ -232,6 +232,7 @@ define([
|
|||||||
this.actions = new api.ActionsAPI(this);
|
this.actions = new api.ActionsAPI(this);
|
||||||
|
|
||||||
this.status = new api.StatusAPI(this);
|
this.status = new api.StatusAPI(this);
|
||||||
|
this.styleManager = new api.StyleManagerAPI(this);
|
||||||
|
|
||||||
this.priority = api.PriorityAPI;
|
this.priority = api.PriorityAPI;
|
||||||
|
|
||||||
@ -241,7 +242,6 @@ define([
|
|||||||
this.branding = BrandingAPI.default;
|
this.branding = BrandingAPI.default;
|
||||||
|
|
||||||
// Plugins that are installed by default
|
// Plugins that are installed by default
|
||||||
|
|
||||||
this.install(this.plugins.Plot());
|
this.install(this.plugins.Plot());
|
||||||
this.install(this.plugins.Chart());
|
this.install(this.plugins.Chart());
|
||||||
this.install(this.plugins.TelemetryTable.default());
|
this.install(this.plugins.TelemetryTable.default());
|
||||||
@ -269,7 +269,6 @@ define([
|
|||||||
this.install(this.plugins.ViewDatumAction());
|
this.install(this.plugins.ViewDatumAction());
|
||||||
this.install(this.plugins.ViewLargeAction());
|
this.install(this.plugins.ViewLargeAction());
|
||||||
this.install(this.plugins.ObjectInterceptors());
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
this.install(this.plugins.NonEditableFolder());
|
|
||||||
this.install(this.plugins.DeviceClassifier());
|
this.install(this.plugins.DeviceClassifier());
|
||||||
this.install(this.plugins.UserIndicator());
|
this.install(this.plugins.UserIndicator());
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ define([
|
|||||||
'./objects/ObjectAPI',
|
'./objects/ObjectAPI',
|
||||||
'./priority/PriorityAPI',
|
'./priority/PriorityAPI',
|
||||||
'./status/StatusAPI',
|
'./status/StatusAPI',
|
||||||
|
'./styles/StyleManagerAPI',
|
||||||
'./telemetry/TelemetryAPI',
|
'./telemetry/TelemetryAPI',
|
||||||
'./time/TimeAPI',
|
'./time/TimeAPI',
|
||||||
'./types/TypeRegistry',
|
'./types/TypeRegistry',
|
||||||
@ -46,6 +47,7 @@ define([
|
|||||||
ObjectAPI,
|
ObjectAPI,
|
||||||
PriorityAPI,
|
PriorityAPI,
|
||||||
StatusAPI,
|
StatusAPI,
|
||||||
|
StyleManagerAPI,
|
||||||
TelemetryAPI,
|
TelemetryAPI,
|
||||||
TimeAPI,
|
TimeAPI,
|
||||||
TypeRegistry,
|
TypeRegistry,
|
||||||
@ -62,6 +64,7 @@ define([
|
|||||||
ObjectAPI: ObjectAPI,
|
ObjectAPI: ObjectAPI,
|
||||||
PriorityAPI: PriorityAPI.default,
|
PriorityAPI: PriorityAPI.default,
|
||||||
StatusAPI: StatusAPI.default,
|
StatusAPI: StatusAPI.default,
|
||||||
|
StyleManagerAPI: StyleManagerAPI.default,
|
||||||
TelemetryAPI: TelemetryAPI,
|
TelemetryAPI: TelemetryAPI,
|
||||||
TimeAPI: TimeAPI.default,
|
TimeAPI: TimeAPI.default,
|
||||||
TypeRegistry: TypeRegistry,
|
TypeRegistry: TypeRegistry,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
|
import AutoCompleteField from './components/controls/AutoCompleteField.vue';
|
||||||
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
|
import ClockDisplayFormatField from './components/controls/ClockDisplayFormatField.vue';
|
||||||
|
import CheckBoxField from './components/controls/CheckBoxField.vue';
|
||||||
import Datetime from './components/controls/Datetime.vue';
|
import Datetime from './components/controls/Datetime.vue';
|
||||||
import FileInput from './components/controls/FileInput.vue';
|
import FileInput from './components/controls/FileInput.vue';
|
||||||
import Locator from './components/controls/Locator.vue';
|
import Locator from './components/controls/Locator.vue';
|
||||||
@ -7,11 +8,13 @@ import NumberField from './components/controls/NumberField.vue';
|
|||||||
import SelectField from './components/controls/SelectField.vue';
|
import SelectField from './components/controls/SelectField.vue';
|
||||||
import TextAreaField from './components/controls/TextAreaField.vue';
|
import TextAreaField from './components/controls/TextAreaField.vue';
|
||||||
import TextField from './components/controls/TextField.vue';
|
import TextField from './components/controls/TextField.vue';
|
||||||
|
import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
export const DEFAULT_CONTROLS_MAP = {
|
export const DEFAULT_CONTROLS_MAP = {
|
||||||
'autocomplete': AutoCompleteField,
|
'autocomplete': AutoCompleteField,
|
||||||
|
'checkbox': CheckBoxField,
|
||||||
'composite': ClockDisplayFormatField,
|
'composite': ClockDisplayFormatField,
|
||||||
'datetime': Datetime,
|
'datetime': Datetime,
|
||||||
'file-input': FileInput,
|
'file-input': FileInput,
|
||||||
@ -19,7 +22,8 @@ export const DEFAULT_CONTROLS_MAP = {
|
|||||||
'numberfield': NumberField,
|
'numberfield': NumberField,
|
||||||
'select': SelectField,
|
'select': SelectField,
|
||||||
'textarea': TextAreaField,
|
'textarea': TextAreaField,
|
||||||
'textfield': TextField
|
'textfield': TextField,
|
||||||
|
'toggleSwitch': ToggleSwitchField
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FormControl {
|
export default class FormControl {
|
||||||
@ -65,10 +69,11 @@ export default class FormControl {
|
|||||||
*/
|
*/
|
||||||
_getControlViewProvider(control) {
|
_getControlViewProvider(control) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
let rowComponent;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
show(element, model, onChange) {
|
show(element, model, onChange) {
|
||||||
const rowComponent = new Vue({
|
rowComponent = new Vue({
|
||||||
el: element,
|
el: element,
|
||||||
components: {
|
components: {
|
||||||
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
|
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
|
||||||
@ -86,8 +91,10 @@ export default class FormControl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return rowComponent;
|
return rowComponent;
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
rowComponent.$destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,26 +26,31 @@
|
|||||||
<div class="c-overlay__dialog-title">{{ model.title }}</div>
|
<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 class="c-overlay__dialog-hint hint">All fields marked <span class="req icon-asterisk"></span> are required.</div>
|
||||||
</div>
|
</div>
|
||||||
<form name="mctForm"
|
<form
|
||||||
|
name="mctForm"
|
||||||
class="c-form__contents"
|
class="c-form__contents"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@submit.prevent
|
@submit.prevent
|
||||||
>
|
>
|
||||||
<div v-for="section in formSections"
|
<div
|
||||||
|
v-for="section in formSections"
|
||||||
:key="section.id"
|
:key="section.id"
|
||||||
class="c-form__section"
|
class="c-form__section"
|
||||||
:class="section.cssClass"
|
:class="section.cssClass"
|
||||||
>
|
>
|
||||||
<h2 v-if="section.name"
|
<h2
|
||||||
|
v-if="section.name"
|
||||||
class="c-form__section-header"
|
class="c-form__section-header"
|
||||||
>
|
>
|
||||||
{{ section.name }}
|
{{ section.name }}
|
||||||
</h2>
|
</h2>
|
||||||
<div v-for="(row, index) in section.rows"
|
<div
|
||||||
|
v-for="(row, index) in section.rows"
|
||||||
:key="row.id"
|
:key="row.id"
|
||||||
class="u-contents"
|
class="u-contents"
|
||||||
>
|
>
|
||||||
<FormRow :css-class="section.cssClass"
|
<FormRow
|
||||||
|
:css-class="section.cssClass"
|
||||||
:first="index < 1"
|
:first="index < 1"
|
||||||
:row="row"
|
:row="row"
|
||||||
@onChange="onChange"
|
@onChange="onChange"
|
||||||
@ -55,14 +60,16 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mct-form__controls c-overlay__button-bar c-form__bottom-bar">
|
<div class="mct-form__controls c-overlay__button-bar c-form__bottom-bar">
|
||||||
<button tabindex="0"
|
<button
|
||||||
|
tabindex="0"
|
||||||
:disabled="isInvalid"
|
:disabled="isInvalid"
|
||||||
class="c-button c-button--major"
|
class="c-button c-button--major"
|
||||||
@click="onSave"
|
@click="onSave"
|
||||||
>
|
>
|
||||||
{{ submitLabel }}
|
{{ submitLabel }}
|
||||||
</button>
|
</button>
|
||||||
<button tabindex="0"
|
<button
|
||||||
|
tabindex="0"
|
||||||
class="c-button"
|
class="c-button"
|
||||||
@click="onDismiss"
|
@click="onDismiss"
|
||||||
>
|
>
|
||||||
|
@ -21,20 +21,24 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="form-row c-form__row"
|
<div
|
||||||
|
class="form-row c-form__row"
|
||||||
:class="[{ 'first': first }]"
|
:class="[{ 'first': first }]"
|
||||||
@onChange="onChange"
|
@onChange="onChange"
|
||||||
>
|
>
|
||||||
<div class="c-form-row__label"
|
<div
|
||||||
|
class="c-form-row__label"
|
||||||
:title="row.description"
|
:title="row.description"
|
||||||
>
|
>
|
||||||
{{ row.name }}
|
{{ row.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="c-form-row__state-indicator"
|
<div
|
||||||
|
class="c-form-row__state-indicator"
|
||||||
:class="rowClass"
|
:class="rowClass"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="row.control"
|
<div
|
||||||
|
v-if="row.control"
|
||||||
class="c-form-row__controls"
|
class="c-form-row__controls"
|
||||||
>
|
>
|
||||||
<div ref="rowElement"></div>
|
<div ref="rowElement"></div>
|
||||||
@ -75,10 +79,12 @@ export default {
|
|||||||
rowClass() {
|
rowClass() {
|
||||||
let cssClass = this.cssClass;
|
let cssClass = this.cssClass;
|
||||||
|
|
||||||
if (this.row.required) {
|
if (!this.row.required) {
|
||||||
cssClass = `${cssClass} req`;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cssClass = `${cssClass} req`;
|
||||||
|
|
||||||
if (this.visited && this.valid !== undefined) {
|
if (this.visited && this.valid !== undefined) {
|
||||||
if (this.valid === true) {
|
if (this.valid === true) {
|
||||||
cssClass = `${cssClass} valid`;
|
cssClass = `${cssClass} valid`;
|
||||||
|
@ -22,20 +22,26 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="form-control autocomplete">
|
<div class="form-control autocomplete">
|
||||||
<input v-model="field"
|
<span class="autocompleteInputAndArrow">
|
||||||
|
<input
|
||||||
|
v-model="field"
|
||||||
class="autocompleteInput"
|
class="autocompleteInput"
|
||||||
type="text"
|
type="text"
|
||||||
@click="inputClicked()"
|
@click="inputClicked()"
|
||||||
@keydown="keyDown($event)"
|
@keydown="keyDown($event)"
|
||||||
>
|
>
|
||||||
<span class="icon-arrow-down"
|
<span
|
||||||
|
class="icon-arrow-down"
|
||||||
@click="arrowClicked()"
|
@click="arrowClicked()"
|
||||||
></span>
|
></span>
|
||||||
<div class="autocompleteOptions"
|
</span>
|
||||||
|
<div
|
||||||
|
class="autocompleteOptions"
|
||||||
@blur="hideOptions = true"
|
@blur="hideOptions = true"
|
||||||
>
|
>
|
||||||
<ul v-if="!hideOptions">
|
<ul v-if="!hideOptions">
|
||||||
<li v-for="opt in filteredOptions"
|
<li
|
||||||
|
v-for="opt in filteredOptions"
|
||||||
:key="opt.optionId"
|
:key="opt.optionId"
|
||||||
:class="{'optionPreSelected': optionIndex === opt.optionId}"
|
:class="{'optionPreSelected': optionIndex === opt.optionId}"
|
||||||
@click="fillInputWithString(opt.name)"
|
@click="fillInputWithString(opt.name)"
|
||||||
@ -104,10 +110,21 @@ export default {
|
|||||||
|
|
||||||
this.$emit('onChange', data);
|
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() {
|
mounted() {
|
||||||
this.options = this.model.options;
|
this.options = this.model.options;
|
||||||
|
this.autocompleteInputAndArrow = this.$el.getElementsByClassName('autocompleteInputAndArrow')[0];
|
||||||
this.autocompleteInputElement = this.$el.getElementsByClassName('autocompleteInput')[0];
|
this.autocompleteInputElement = this.$el.getElementsByClassName('autocompleteInput')[0];
|
||||||
if (this.options[0].name) {
|
if (this.options[0].name) {
|
||||||
// If "options" include name, value pair
|
// If "options" include name, value pair
|
||||||
@ -119,6 +136,9 @@ export default {
|
|||||||
this.optionNames = this.options;
|
this.optionNames = this.options;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
destroyed() {
|
||||||
|
document.body.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
decrementOptionIndex() {
|
decrementOptionIndex() {
|
||||||
if (this.optionIndex === 0) {
|
if (this.optionIndex === 0) {
|
||||||
@ -172,7 +192,21 @@ export default {
|
|||||||
// to show them all the options
|
// to show them all the options
|
||||||
this.showFilteredOptions = false;
|
this.showFilteredOptions = false;
|
||||||
this.autocompleteInputElement.select();
|
this.autocompleteInputElement.select();
|
||||||
|
|
||||||
|
if (this.hideOptions) {
|
||||||
this.showOptions();
|
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;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
optionMouseover(optionId) {
|
optionMouseover(optionId) {
|
||||||
this.optionIndex = optionId;
|
this.optionIndex = optionId;
|
||||||
|
55
src/api/forms/components/controls/CheckBoxField.vue
Normal file
55
src/api/forms/components/controls/CheckBoxField.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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,7 +22,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-form-control--clock-display-format-fields">
|
<div class="c-form-control--clock-display-format-fields">
|
||||||
<SelectField v-for="item in items"
|
<SelectField
|
||||||
|
v-for="item in items"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:model="item"
|
:model="item"
|
||||||
@onChange="onChange"
|
@onChange="onChange"
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<CompositeItem v-for="(item, index) in model.items"
|
<CompositeItem
|
||||||
|
v-for="(item, index) in model.items"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:first="index < 1"
|
:first="index < 1"
|
||||||
:value="JSON.stringify(model.value[index])"
|
:value="JSON.stringify(model.value[index])"
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="compositeCssClass">
|
<div :class="compositeCssClass">
|
||||||
<FormRow :css-class="item.cssClass"
|
<FormRow
|
||||||
|
:css-class="item.cssClass"
|
||||||
:first="first"
|
:first="first"
|
||||||
:row="row"
|
:row="row"
|
||||||
@onChange="onChange"
|
@onChange="onChange"
|
||||||
|
@ -27,12 +27,14 @@
|
|||||||
<div class="hint time sm">Min</div>
|
<div class="hint time sm">Min</div>
|
||||||
<div class="hint time sm">Sec</div>
|
<div class="hint time sm">Sec</div>
|
||||||
<div class="hint timezone">Timezone</div>
|
<div class="hint timezone">Timezone</div>
|
||||||
<form ref="dateTimeForm"
|
<form
|
||||||
|
ref="dateTimeForm"
|
||||||
prevent
|
prevent
|
||||||
class="u-contents"
|
class="u-contents"
|
||||||
>
|
>
|
||||||
<div class="field control date">
|
<div class="field control date">
|
||||||
<input v-model="date"
|
<input
|
||||||
|
v-model="date"
|
||||||
:pattern="/\d{4}-\d{2}-\d{2}/"
|
:pattern="/\d{4}-\d{2}-\d{2}/"
|
||||||
:placeholder="format"
|
:placeholder="format"
|
||||||
type="date"
|
type="date"
|
||||||
@ -41,7 +43,8 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="field control hour sm">
|
<div class="field control hour sm">
|
||||||
<input v-model="hour"
|
<input
|
||||||
|
v-model="hour"
|
||||||
:pattern="/\d+/"
|
:pattern="/\d+/"
|
||||||
type="number"
|
type="number"
|
||||||
name="hour"
|
name="hour"
|
||||||
@ -52,7 +55,8 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="field control min sm">
|
<div class="field control min sm">
|
||||||
<input v-model="min"
|
<input
|
||||||
|
v-model="min"
|
||||||
:pattern="/\d+/"
|
:pattern="/\d+/"
|
||||||
type="number"
|
type="number"
|
||||||
name="min"
|
name="min"
|
||||||
@ -63,7 +67,8 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="field control sec sm">
|
<div class="field control sec sm">
|
||||||
<input v-model="sec"
|
<input
|
||||||
|
v-model="sec"
|
||||||
:pattern="/\d+/"
|
:pattern="/\d+/"
|
||||||
type="number"
|
type="number"
|
||||||
name="sec"
|
name="sec"
|
||||||
|
@ -22,16 +22,19 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="form-control shell">
|
<span class="form-control shell">
|
||||||
<span class="field control"
|
<span
|
||||||
|
class="field control"
|
||||||
:class="model.cssClass"
|
:class="model.cssClass"
|
||||||
>
|
>
|
||||||
<input id="fileElem"
|
<input
|
||||||
|
id="fileElem"
|
||||||
ref="fileInput"
|
ref="fileInput"
|
||||||
type="file"
|
type="file"
|
||||||
accept=".json"
|
accept=".json"
|
||||||
style="display:none"
|
style="display:none"
|
||||||
>
|
>
|
||||||
<button id="fileSelect"
|
<button
|
||||||
|
id="fileSelect"
|
||||||
class="c-button"
|
class="c-button"
|
||||||
@click="selectFile"
|
@click="selectFile"
|
||||||
>
|
>
|
||||||
|
@ -22,21 +22,25 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="form-control shell">
|
<span class="form-control shell">
|
||||||
<span class="field control"
|
<span
|
||||||
|
class="field control"
|
||||||
:class="model.cssClass"
|
:class="model.cssClass"
|
||||||
>
|
>
|
||||||
<input v-model="field"
|
<input
|
||||||
|
v-model="field"
|
||||||
type="number"
|
type="number"
|
||||||
:min="model.min"
|
:min="model.min"
|
||||||
:max="model.max"
|
:max="model.max"
|
||||||
:step="model.step"
|
:step="model.step"
|
||||||
@blur="blur()"
|
@input="updateText()"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
@ -49,8 +53,11 @@ export default {
|
|||||||
field: this.model.value
|
field: this.model.value
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateText = throttle(this.updateText.bind(this), 200);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
blur() {
|
updateText() {
|
||||||
const data = {
|
const data = {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
value: this.field
|
value: this.field
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="form-control select-field">
|
<div class="form-control select-field">
|
||||||
<select v-model="selected"
|
<select
|
||||||
|
v-model="selected"
|
||||||
required="model.required"
|
required="model.required"
|
||||||
name="mctControl"
|
name="mctControl"
|
||||||
@change="onChange($event)"
|
@change="onChange($event)"
|
||||||
>
|
>
|
||||||
<option v-for="option in model.options"
|
<option
|
||||||
|
v-for="option in model.options"
|
||||||
:key="option.name"
|
:key="option.name"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
>
|
>
|
||||||
|
@ -22,13 +22,15 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="form-control shell">
|
<span class="form-control shell">
|
||||||
<span class="field control"
|
<span
|
||||||
|
class="field control"
|
||||||
:class="model.cssClass"
|
:class="model.cssClass"
|
||||||
>
|
>
|
||||||
<textarea v-model="field"
|
<textarea
|
||||||
|
v-model="field"
|
||||||
type="text"
|
type="text"
|
||||||
:size="model.size"
|
:size="model.size"
|
||||||
@blur="blur()"
|
@input="updateText()"
|
||||||
>
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
</span>
|
</span>
|
||||||
@ -36,6 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
@ -48,8 +52,11 @@ export default {
|
|||||||
field: this.model.value
|
field: this.model.value
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateText = throttle(this.updateText.bind(this), 500);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
blur() {
|
updateText() {
|
||||||
const data = {
|
const data = {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
value: this.field
|
value: this.field
|
||||||
|
@ -22,19 +22,23 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="form-control shell">
|
<span class="form-control shell">
|
||||||
<span class="field control"
|
<span
|
||||||
|
class="field control"
|
||||||
:class="model.cssClass"
|
:class="model.cssClass"
|
||||||
>
|
>
|
||||||
<input v-model="field"
|
<input
|
||||||
|
v-model="field"
|
||||||
type="text"
|
type="text"
|
||||||
:size="model.size"
|
:size="model.size"
|
||||||
@blur="blur()"
|
@input="updateText()"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
@ -47,8 +51,11 @@ export default {
|
|||||||
field: this.model.value
|
field: this.model.value
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateText = throttle(this.updateText.bind(this), 500);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
blur() {
|
updateText() {
|
||||||
const data = {
|
const data = {
|
||||||
model: this.model,
|
model: this.model,
|
||||||
value: this.field
|
value: this.field
|
||||||
|
62
src/api/forms/components/controls/ToggleSwitchField.vue
Normal file
62
src/api/forms/components/controls/ToggleSwitchField.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
<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>
|
19
src/api/forms/toggle-check-box-mixin.js
Normal file
19
src/api/forms/toggle-check-box-mixin.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-menu"
|
<div
|
||||||
|
class="c-menu"
|
||||||
:class="options.menuClass"
|
:class="options.menuClass"
|
||||||
>
|
>
|
||||||
<ul v-if="options.actions.length && options.actions[0].length">
|
<ul v-if="options.actions.length && options.actions[0].length">
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-menu"
|
<div
|
||||||
|
class="c-menu"
|
||||||
:class="[options.menuClass, 'c-super-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"
|
class="c-super-menu__menu"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
@ -34,7 +36,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul v-else
|
<ul
|
||||||
|
v-else
|
||||||
class="c-super-menu__menu"
|
class="c-super-menu__menu"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
|
@ -43,6 +43,7 @@ class InMemorySearchProvider {
|
|||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
|
||||||
this.indexedIds = {};
|
this.indexedIds = {};
|
||||||
|
this.indexedCompositions = {};
|
||||||
this.idsToIndex = [];
|
this.idsToIndex = [];
|
||||||
this.pendingIndex = {};
|
this.pendingIndex = {};
|
||||||
this.pendingRequests = 0;
|
this.pendingRequests = 0;
|
||||||
@ -58,7 +59,6 @@ class InMemorySearchProvider {
|
|||||||
this.onWorkerMessageError = this.onWorkerMessageError.bind(this);
|
this.onWorkerMessageError = this.onWorkerMessageError.bind(this);
|
||||||
this.onerror = this.onWorkerError.bind(this);
|
this.onerror = this.onWorkerError.bind(this);
|
||||||
this.startIndexing = this.startIndexing.bind(this);
|
this.startIndexing = this.startIndexing.bind(this);
|
||||||
this.onMutationOfIndexedObject = this.onMutationOfIndexedObject.bind(this);
|
|
||||||
|
|
||||||
this.openmct.on('start', this.startIndexing);
|
this.openmct.on('start', this.startIndexing);
|
||||||
this.openmct.on('destroy', () => {
|
this.openmct.on('destroy', () => {
|
||||||
@ -68,6 +68,9 @@ class InMemorySearchProvider {
|
|||||||
this.worker.port.onmessageerror = null;
|
this.worker.port.onmessageerror = null;
|
||||||
this.worker.port.close();
|
this.worker.port.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.destroyObservers(this.indexedIds);
|
||||||
|
this.destroyObservers(this.indexedCompositions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +140,7 @@ class InMemorySearchProvider {
|
|||||||
};
|
};
|
||||||
modelResults.hits = await Promise.all(event.data.results.map(async (hit) => {
|
modelResults.hits = await Promise.all(event.data.results.map(async (hit) => {
|
||||||
const identifier = this.openmct.objects.parseKeyString(hit.keyString);
|
const identifier = this.openmct.objects.parseKeyString(hit.keyString);
|
||||||
const domainObject = await this.openmct.objects.get(identifier.key);
|
const domainObject = await this.openmct.objects.get(identifier);
|
||||||
|
|
||||||
return domainObject;
|
return domainObject;
|
||||||
}));
|
}));
|
||||||
@ -213,29 +216,52 @@ class InMemorySearchProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMutationOfIndexedObject(domainObject) {
|
onNameMutation(domainObject, name) {
|
||||||
const provider = this;
|
const provider = this;
|
||||||
provider.index(domainObject.identifier, domainObject);
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pass an id and model to the worker to be indexed. If the model has
|
* Pass a domainObject to the worker to be indexed.
|
||||||
* composition, schedule those ids for later indexing.
|
* If the object has composition, schedule those ids for later indexing.
|
||||||
|
* Watch for object changes and re-index object and children if so
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param id a model id
|
* @param domainObject a domainObject
|
||||||
* @param model a model
|
|
||||||
*/
|
*/
|
||||||
async index(id, domainObject) {
|
async index(domainObject) {
|
||||||
const provider = this;
|
const provider = this;
|
||||||
const keyString = this.openmct.objects.makeKeyString(id);
|
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||||
|
|
||||||
if (!this.indexedIds[keyString]) {
|
if (!this.indexedIds[keyString]) {
|
||||||
this.openmct.objects.observe(domainObject, `*`, this.onMutationOfIndexedObject);
|
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.indexedIds[keyString] = true;
|
if ((keyString !== 'ROOT')) {
|
||||||
|
|
||||||
if ((id.key !== 'ROOT')) {
|
|
||||||
if (this.worker) {
|
if (this.worker) {
|
||||||
this.worker.port.postMessage({
|
this.worker.port.postMessage({
|
||||||
request: 'index',
|
request: 'index',
|
||||||
@ -247,15 +273,12 @@ class InMemorySearchProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const composition = this.openmct.composition.registry.find(foundComposition => {
|
const composition = this.openmct.composition.get(domainObject);
|
||||||
return foundComposition.appliesTo(domainObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (composition) {
|
if (composition !== undefined) {
|
||||||
const childIdentifiers = await composition.load(domainObject);
|
const children = await composition.load();
|
||||||
childIdentifiers.forEach(function (childIdentifier) {
|
|
||||||
provider.scheduleForIndexing(childIdentifier);
|
children.forEach(child => provider.scheduleForIndexing(child.identifier));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,12 +294,12 @@ class InMemorySearchProvider {
|
|||||||
const provider = this;
|
const provider = this;
|
||||||
|
|
||||||
this.pendingRequests += 1;
|
this.pendingRequests += 1;
|
||||||
const identifier = await this.openmct.objects.parseKeyString(keyString);
|
const domainObject = await this.openmct.objects.get(keyString);
|
||||||
const domainObject = await this.openmct.objects.get(identifier.key);
|
|
||||||
delete provider.pendingIndex[keyString];
|
delete provider.pendingIndex[keyString];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (domainObject) {
|
if (domainObject) {
|
||||||
await provider.index(identifier, domainObject);
|
await provider.index(domainObject);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to index domain object ' + keyString, error);
|
console.warn('Failed to index domain object ' + keyString, error);
|
||||||
@ -347,6 +370,16 @@ class InMemorySearchProvider {
|
|||||||
};
|
};
|
||||||
this.onWorkerMessage(eventToReturn);
|
this.onWorkerMessage(eventToReturn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroyObservers(observers) {
|
||||||
|
Object.entries(observers).forEach(([keyString, unobserve]) => {
|
||||||
|
if (typeof unobserve === 'function') {
|
||||||
|
unobserve();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete observers[keyString];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InMemorySearchProvider;
|
export default InMemorySearchProvider;
|
||||||
|
@ -105,13 +105,18 @@ describe("The Object API Search Function", () => {
|
|||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
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, "query").and.callThrough();
|
||||||
spyOn(openmct.objects.inMemorySearchProvider, "localSearch").and.callThrough();
|
spyOn(openmct.objects.inMemorySearchProvider, "localSearch").and.callThrough();
|
||||||
|
|
||||||
openmct.on('start', async () => {
|
openmct.on('start', async () => {
|
||||||
mockIdentifier1 = {
|
mockIdentifier1 = {
|
||||||
key: 'some-object',
|
key: 'some-object',
|
||||||
namespace: 'some-namespace'
|
namespace: 'foo'
|
||||||
};
|
};
|
||||||
mockDomainObject1 = {
|
mockDomainObject1 = {
|
||||||
type: 'clock',
|
type: 'clock',
|
||||||
@ -120,7 +125,7 @@ describe("The Object API Search Function", () => {
|
|||||||
};
|
};
|
||||||
mockIdentifier2 = {
|
mockIdentifier2 = {
|
||||||
key: 'some-other-object',
|
key: 'some-other-object',
|
||||||
namespace: 'some-namespace'
|
namespace: 'foo'
|
||||||
};
|
};
|
||||||
mockDomainObject2 = {
|
mockDomainObject2 = {
|
||||||
type: 'clock',
|
type: 'clock',
|
||||||
@ -129,16 +134,16 @@ describe("The Object API Search Function", () => {
|
|||||||
};
|
};
|
||||||
mockIdentifier3 = {
|
mockIdentifier3 = {
|
||||||
key: 'yet-another-object',
|
key: 'yet-another-object',
|
||||||
namespace: 'some-namespace'
|
namespace: 'foo'
|
||||||
};
|
};
|
||||||
mockDomainObject3 = {
|
mockDomainObject3 = {
|
||||||
type: 'clock',
|
type: 'clock',
|
||||||
name: 'redBear',
|
name: 'redBear',
|
||||||
identifier: mockIdentifier3
|
identifier: mockIdentifier3
|
||||||
};
|
};
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockIdentifier1, mockDomainObject1);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject1);
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockIdentifier2, mockDomainObject2);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject2);
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockIdentifier3, mockDomainObject3);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject3);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
openmct.startHeadless();
|
openmct.startHeadless();
|
||||||
@ -175,9 +180,9 @@ describe("The Object API Search Function", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
openmct.objects.inMemorySearchProvider.worker = null;
|
openmct.objects.inMemorySearchProvider.worker = null;
|
||||||
// reindex locally
|
// reindex locally
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockIdentifier1, mockDomainObject1);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject1);
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockIdentifier2, mockDomainObject2);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject2);
|
||||||
await openmct.objects.inMemorySearchProvider.index(mockIdentifier3, mockDomainObject3);
|
await openmct.objects.inMemorySearchProvider.index(mockDomainObject3);
|
||||||
});
|
});
|
||||||
it("calls local search", () => {
|
it("calls local search", () => {
|
||||||
openmct.objects.search('foo');
|
openmct.objects.search('foo');
|
||||||
|
@ -22,12 +22,14 @@
|
|||||||
|
|
||||||
export default class Transaction {
|
export default class Transaction {
|
||||||
constructor(objectAPI) {
|
constructor(objectAPI) {
|
||||||
this.dirtyObjects = new Set();
|
this.dirtyObjects = {};
|
||||||
this.objectAPI = objectAPI;
|
this.objectAPI = objectAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(object) {
|
add(object) {
|
||||||
this.dirtyObjects.add(object);
|
const key = this.objectAPI.makeKeyString(object.identifier);
|
||||||
|
|
||||||
|
this.dirtyObjects[key] = object;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
@ -37,7 +39,8 @@ export default class Transaction {
|
|||||||
commit() {
|
commit() {
|
||||||
const promiseArray = [];
|
const promiseArray = [];
|
||||||
const save = this.objectAPI.save.bind(this.objectAPI);
|
const save = this.objectAPI.save.bind(this.objectAPI);
|
||||||
this.dirtyObjects.forEach(object => {
|
|
||||||
|
Object.values(this.dirtyObjects).forEach(object => {
|
||||||
promiseArray.push(this.createDirtyObjectPromise(object, save));
|
promiseArray.push(this.createDirtyObjectPromise(object, save));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,7 +51,9 @@ export default class Transaction {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
action(object)
|
action(object)
|
||||||
.then((success) => {
|
.then((success) => {
|
||||||
this.dirtyObjects.delete(object);
|
const key = this.objectAPI.makeKeyString(object.identifier);
|
||||||
|
|
||||||
|
delete this.dirtyObjects[key];
|
||||||
resolve(success);
|
resolve(success);
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
@ -57,7 +62,8 @@ export default class Transaction {
|
|||||||
|
|
||||||
getDirtyObject(identifier) {
|
getDirtyObject(identifier) {
|
||||||
let dirtyObject;
|
let dirtyObject;
|
||||||
this.dirtyObjects.forEach(object => {
|
|
||||||
|
Object.values(this.dirtyObjects).forEach(object => {
|
||||||
const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier);
|
const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier);
|
||||||
if (areIdsEqual) {
|
if (areIdsEqual) {
|
||||||
dirtyObject = object;
|
dirtyObject = object;
|
||||||
@ -67,14 +73,11 @@ export default class Transaction {
|
|||||||
return dirtyObject;
|
return dirtyObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
|
||||||
this.dirtyObjects = new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
_clear() {
|
_clear() {
|
||||||
const promiseArray = [];
|
const promiseArray = [];
|
||||||
const refresh = this.objectAPI.refresh.bind(this.objectAPI);
|
const refresh = this.objectAPI.refresh.bind(this.objectAPI);
|
||||||
this.dirtyObjects.forEach(object => {
|
|
||||||
|
Object.values(this.dirtyObjects).forEach(object => {
|
||||||
promiseArray.push(this.createDirtyObjectPromise(object, refresh));
|
promiseArray.push(this.createDirtyObjectPromise(object, refresh));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -34,24 +34,24 @@ describe("Transaction Class", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('has no dirty objects', () => {
|
it('has no dirty objects', () => {
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add(), adds object to dirtyObjects', () => {
|
it('add(), adds object to dirtyObjects', () => {
|
||||||
const mockDomainObjects = createMockDomainObjects();
|
const mockDomainObjects = createMockDomainObjects();
|
||||||
transaction.add(mockDomainObjects[0]);
|
transaction.add(mockDomainObjects[0]);
|
||||||
expect(transaction.dirtyObjects.size).toEqual(1);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cancel(), clears all dirtyObjects', (done) => {
|
it('cancel(), clears all dirtyObjects', (done) => {
|
||||||
const mockDomainObjects = createMockDomainObjects(3);
|
const mockDomainObjects = createMockDomainObjects(3);
|
||||||
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(3);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(3);
|
||||||
|
|
||||||
transaction.cancel()
|
transaction.cancel()
|
||||||
.then(success => {
|
.then(success => {
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
}).finally(done);
|
}).finally(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -59,12 +59,12 @@ describe("Transaction Class", () => {
|
|||||||
const mockDomainObjects = createMockDomainObjects(3);
|
const mockDomainObjects = createMockDomainObjects(3);
|
||||||
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
mockDomainObjects.forEach(transaction.add.bind(transaction));
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(3);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(3);
|
||||||
spyOn(objectAPI, 'save').and.callThrough();
|
spyOn(objectAPI, 'save').and.callThrough();
|
||||||
|
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
.then(success => {
|
.then(success => {
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
expect(objectAPI.save.calls.count()).toEqual(3);
|
expect(objectAPI.save.calls.count()).toEqual(3);
|
||||||
}).finally(done);
|
}).finally(done);
|
||||||
});
|
});
|
||||||
@ -73,7 +73,7 @@ describe("Transaction Class", () => {
|
|||||||
const mockDomainObjects = createMockDomainObjects();
|
const mockDomainObjects = createMockDomainObjects();
|
||||||
transaction.add(mockDomainObjects[0]);
|
transaction.add(mockDomainObjects[0]);
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(1);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(1);
|
||||||
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
||||||
|
|
||||||
expect(dirtyObject).toEqual(mockDomainObjects[0]);
|
expect(dirtyObject).toEqual(mockDomainObjects[0]);
|
||||||
@ -82,7 +82,7 @@ describe("Transaction Class", () => {
|
|||||||
it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => {
|
it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => {
|
||||||
const mockDomainObjects = createMockDomainObjects();
|
const mockDomainObjects = createMockDomainObjects();
|
||||||
|
|
||||||
expect(transaction.dirtyObjects.size).toEqual(0);
|
expect(Object.keys(transaction.dirtyObjects).length).toEqual(0);
|
||||||
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier);
|
||||||
|
|
||||||
expect(dirtyObject).toEqual(undefined);
|
expect(dirtyObject).toEqual(undefined);
|
||||||
|
67
src/api/styles/StyleManagerAPI.js
Normal file
67
src/api/styles/StyleManagerAPI.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
|
export default class StyleManagerAPI extends EventEmitter {
|
||||||
|
constructor(openmct) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._openmct = openmct;
|
||||||
|
this._styleCache = {};
|
||||||
|
|
||||||
|
this.get = this.get.bind(this);
|
||||||
|
this.set = this.set.bind(this);
|
||||||
|
this.observe = this.observe.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(identifier) {
|
||||||
|
let keyString = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
return this._styleCache[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(identifier, value) {
|
||||||
|
let keyString = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
this._styleCache[keyString] = value;
|
||||||
|
this.emit(keyString, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(identifier) {
|
||||||
|
let keyString = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
this._styleCache[keyString] = undefined;
|
||||||
|
this.emit(keyString, undefined);
|
||||||
|
delete this._styleCache[keyString];
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(identifier, callback) {
|
||||||
|
let key = this._openmct.objects.makeKeyString(identifier);
|
||||||
|
|
||||||
|
this.on(key, callback);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.off(key, callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -172,6 +172,7 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_processNewTelemetry(telemetryData) {
|
_processNewTelemetry(telemetryData) {
|
||||||
|
performance.mark('tlm:process:start');
|
||||||
if (telemetryData === undefined) {
|
if (telemetryData === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -184,8 +185,8 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
|
|
||||||
for (let datum of data) {
|
for (let datum of data) {
|
||||||
parsedValue = this.parseTime(datum);
|
parsedValue = this.parseTime(datum);
|
||||||
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
beforeStartOfBounds = parsedValue <= this.lastBounds.start;
|
||||||
afterEndOfBounds = parsedValue > this.lastBounds.end;
|
afterEndOfBounds = parsedValue >= this.lastBounds.end;
|
||||||
|
|
||||||
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
if (!afterEndOfBounds && !beforeStartOfBounds) {
|
||||||
let isDuplicate = false;
|
let isDuplicate = false;
|
||||||
@ -352,6 +353,7 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
* @todo handle subscriptions more granually
|
* @todo handle subscriptions more granually
|
||||||
*/
|
*/
|
||||||
_reset() {
|
_reset() {
|
||||||
|
performance.mark('tlm:reset');
|
||||||
this.boundedTelemetry = [];
|
this.boundedTelemetry = [];
|
||||||
this.futureBuffer = [];
|
this.futureBuffer = [];
|
||||||
|
|
||||||
|
@ -365,3 +365,7 @@ class TimeContext extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default TimeContext;
|
export default TimeContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@typedef {{start: number, end: number}} Bounds
|
||||||
|
*/
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import CSV from 'comma-separated-values';
|
import CSV from 'comma-separated-values';
|
||||||
import {saveAs} from 'file-saver/FileSaver';
|
import {saveAs} from 'saveAs';
|
||||||
|
|
||||||
class CSVExporter {
|
class CSVExporter {
|
||||||
export(rows, options) {
|
export(rows, options) {
|
||||||
|
@ -31,7 +31,7 @@ function replaceDotsWithUnderscores(filename) {
|
|||||||
return filename.replace(regex, '_');
|
return filename.replace(regex, '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
import {saveAs} from 'file-saver/FileSaver';
|
import {saveAs} from 'saveAs';
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import {saveAs} from 'file-saver/FileSaver';
|
import {saveAs} from 'saveAs';
|
||||||
|
|
||||||
class JSONExporter {
|
class JSONExporter {
|
||||||
export(obj, options) {
|
export(obj, options) {
|
||||||
|
@ -15,7 +15,8 @@ export default function (folderName, couchPlugin, searchFilter) {
|
|||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
identifier,
|
identifier,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
name: folderName || "CouchDB Documents"
|
name: folderName || "CouchDB Documents",
|
||||||
|
location: 'ROOT'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,8 @@ describe('the plugin', function () {
|
|||||||
expect(object).toEqual({
|
expect(object).toEqual({
|
||||||
identifier,
|
identifier,
|
||||||
type: 'folder',
|
type: 'folder',
|
||||||
name: "CouchDB Documents"
|
name: 'CouchDB Documents',
|
||||||
|
location: 'ROOT'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* Open MCT, Copyright (c) 2014-2022, United States Government
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
@ -114,14 +113,12 @@ export default {
|
|||||||
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
|
||||||
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
|
||||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||||
this.bounds = this.openmct.time.bounds();
|
|
||||||
|
|
||||||
this.limitEvaluator = this.openmct
|
this.limitEvaluator = this.openmct
|
||||||
.telemetry
|
.telemetry
|
||||||
.limitEvaluator(this.domainObject);
|
.limitEvaluator(this.domainObject);
|
||||||
|
|
||||||
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
||||||
this.openmct.time.on('bounds', this.updateBounds);
|
|
||||||
|
|
||||||
this.timestampKey = this.openmct.time.timeSystem().key;
|
this.timestampKey = this.openmct.time.timeSystem().key;
|
||||||
|
|
||||||
@ -135,72 +132,39 @@ export default {
|
|||||||
|
|
||||||
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
|
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
|
||||||
|
|
||||||
this.unsubscribe = this.openmct
|
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||||
.telemetry
|
size: 1,
|
||||||
.subscribe(this.domainObject, this.setLatestValues);
|
strategy: 'latest'
|
||||||
|
});
|
||||||
this.requestHistory();
|
this.telemetryCollection.on('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.on('clear', this.resetValues);
|
||||||
|
this.telemetryCollection.load();
|
||||||
|
|
||||||
if (this.hasUnits) {
|
if (this.hasUnits) {
|
||||||
this.setUnit();
|
this.setUnit();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
this.unsubscribe();
|
|
||||||
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
this.openmct.time.off('timeSystem', this.updateTimeSystem);
|
||||||
this.openmct.time.off('bounds', this.updateBounds);
|
this.telemetryCollection.off('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.off('clear', this.resetValues);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateView() {
|
updateView() {
|
||||||
if (!this.updatingView) {
|
if (!this.updatingView) {
|
||||||
this.updatingView = true;
|
this.updatingView = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
let newTimestamp = this.getParsedTimestamp(this.latestDatum);
|
this.timestamp = this.getParsedTimestamp(this.latestDatum);
|
||||||
|
|
||||||
if (this.shouldUpdate(newTimestamp)) {
|
|
||||||
this.timestamp = newTimestamp;
|
|
||||||
this.datum = this.latestDatum;
|
this.datum = this.latestDatum;
|
||||||
}
|
|
||||||
|
|
||||||
this.updatingView = false;
|
this.updatingView = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setLatestValues(datum) {
|
setLatestValues(data) {
|
||||||
this.latestDatum = datum;
|
this.latestDatum = data[data.length - 1];
|
||||||
|
|
||||||
this.updateView();
|
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) {
|
updateTimeSystem(timeSystem) {
|
||||||
this.resetValues();
|
|
||||||
this.timestampKey = timeSystem.key;
|
this.timestampKey = timeSystem.key;
|
||||||
},
|
},
|
||||||
updateViewContext() {
|
updateViewContext() {
|
||||||
@ -241,4 +205,3 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
let openmct;
|
let openmct;
|
||||||
let ladPlugin;
|
let ladPlugin;
|
||||||
|
let historicalProvider;
|
||||||
let parent;
|
let parent;
|
||||||
let child;
|
let child;
|
||||||
let telemetryCount = 3;
|
let telemetryCount = 3;
|
||||||
@ -81,6 +82,13 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||||
|
|
||||||
|
historicalProvider = {
|
||||||
|
request: () => {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
|
||||||
|
|
||||||
openmct.time.bounds({
|
openmct.time.bounds({
|
||||||
start: bounds.start,
|
start: bounds.start,
|
||||||
end: bounds.end
|
end: bounds.end
|
||||||
@ -147,7 +155,7 @@ describe("The LAD Table", () => {
|
|||||||
// add another telemetry object as composition in lad table to test multi rows
|
// add another telemetry object as composition in lad table to test multi rows
|
||||||
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
|
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async (done) => {
|
||||||
let telemetryRequestResolve;
|
let telemetryRequestResolve;
|
||||||
let telemetryObjectResolve;
|
let telemetryObjectResolve;
|
||||||
let anotherTelemetryObjectResolve;
|
let anotherTelemetryObjectResolve;
|
||||||
@ -166,11 +174,12 @@ describe("The LAD Table", () => {
|
|||||||
callBack();
|
callBack();
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.telemetry.request.and.callFake(() => {
|
historicalProvider.request = () => {
|
||||||
telemetryRequestResolve(mockTelemetry);
|
telemetryRequestResolve(mockTelemetry);
|
||||||
|
|
||||||
return telemetryRequestPromise;
|
return telemetryRequestPromise;
|
||||||
});
|
};
|
||||||
|
|
||||||
openmct.objects.get.and.callFake((obj) => {
|
openmct.objects.get.and.callFake((obj) => {
|
||||||
if (obj.key === 'telemetry-object') {
|
if (obj.key === 'telemetry-object') {
|
||||||
telemetryObjectResolve(mockObj.telemetry);
|
telemetryObjectResolve(mockObj.telemetry);
|
||||||
@ -195,6 +204,8 @@ describe("The LAD Table", () => {
|
|||||||
|
|
||||||
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
|
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show one row per object in the composition", () => {
|
it("should show one row per object in the composition", () => {
|
||||||
|
@ -1,24 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="plotWrapper"
|
<div
|
||||||
|
ref="plotWrapper"
|
||||||
class="has-local-controls"
|
class="has-local-controls"
|
||||||
:class="{ 's-unsynced' : isZoomed }"
|
:class="{ 's-unsynced' : isZoomed }"
|
||||||
>
|
>
|
||||||
<div v-if="isZoomed"
|
<div
|
||||||
|
v-if="isZoomed"
|
||||||
class="l-state-indicators"
|
class="l-state-indicators"
|
||||||
>
|
>
|
||||||
<span class="l-state-indicators__alert-no-lad t-object-alert t-alert-unsynced icon-alert-triangle"
|
<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."
|
title="This plot is not currently displaying the latest data. Reset pan/zoom to view latest data."
|
||||||
></span>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div ref="plot"
|
<div
|
||||||
|
ref="plot"
|
||||||
class="c-bar-chart"
|
class="c-bar-chart"
|
||||||
@plotly_relayout="zoom"
|
@plotly_relayout="zoom"
|
||||||
></div>
|
></div>
|
||||||
<div v-if="false"
|
<div
|
||||||
|
v-if="false"
|
||||||
ref="localControl"
|
ref="localControl"
|
||||||
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
|
class="gl-plot__local-controls h-local-controls h-local-controls--overlay-content c-local-controls--show-on-hover"
|
||||||
>
|
>
|
||||||
<button v-if="data.length"
|
<button
|
||||||
|
v-if="data.length"
|
||||||
class="c-button icon-reset"
|
class="c-button icon-reset"
|
||||||
:disabled="!isZoomed"
|
:disabled="!isZoomed"
|
||||||
title="Reset pan/zoom"
|
title="Reset pan/zoom"
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BarGraph ref="barGraph"
|
<BarGraph
|
||||||
|
ref="barGraph"
|
||||||
class="c-plot c-bar-chart-view"
|
class="c-plot c-bar-chart-view"
|
||||||
:data="trace"
|
:data="trace"
|
||||||
:plot-axis-title="plotAxisTitle"
|
:plot-axis-title="plotAxisTitle"
|
||||||
|
@ -22,10 +22,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="c-tree c-bar-graph-options">
|
<ul class="c-tree c-bar-graph-options">
|
||||||
<h2 title="Display properties for this object">Bar Graph Series</h2>
|
<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"
|
:key="series.key"
|
||||||
>
|
>
|
||||||
<series-options :item="series"
|
<series-options
|
||||||
|
:item="series"
|
||||||
:color-palette="colorPalette"
|
:color-palette="colorPalette"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<ul>
|
<ul>
|
||||||
<li class="c-tree__item menus-to-left"
|
<li
|
||||||
|
class="c-tree__item menus-to-left"
|
||||||
:class="aliasCss"
|
:class="aliasCss"
|
||||||
>
|
>
|
||||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
<span
|
||||||
|
class="c-disclosure-triangle is-enabled flex-elem"
|
||||||
:class="expandedCssClass"
|
:class="expandedCssClass"
|
||||||
@click="expanded = !expanded"
|
@click="expanded = !expanded"
|
||||||
>
|
>
|
||||||
@ -36,7 +38,8 @@
|
|||||||
<div class="c-object-label__name">{{ name }}</div>
|
<div class="c-object-label__name">{{ name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<ColorSwatch v-if="expanded"
|
<ColorSwatch
|
||||||
|
v-if="expanded"
|
||||||
:current-color="currentColor"
|
:current-color="currentColor"
|
||||||
title="Manually set the color for this bar graph series."
|
title="Manually set the color for this bar graph series."
|
||||||
edit-title="Manually set the color for this bar graph series"
|
edit-title="Manually set the color for this bar graph series"
|
||||||
|
@ -185,10 +185,14 @@ describe('The Clear Data Plugin:', () => {
|
|||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
openmct = createOpenMct();
|
openmct = createOpenMct();
|
||||||
|
|
||||||
clearDataPlugin = new ClearDataPlugin(
|
clearDataPlugin = new ClearDataPlugin([
|
||||||
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
|
'table',
|
||||||
{indicator: true}
|
'telemetry.plot.overlay',
|
||||||
);
|
'telemetry.plot.stacked',
|
||||||
|
'example.imagery'
|
||||||
|
], {
|
||||||
|
indicator: true
|
||||||
|
});
|
||||||
openmct.install(clearDataPlugin);
|
openmct.install(clearDataPlugin);
|
||||||
appHolder = document.createElement('div');
|
appHolder = document.createElement('div');
|
||||||
document.body.appendChild(appHolder);
|
document.body.appendChild(appHolder);
|
||||||
|
@ -25,6 +25,7 @@ import EventEmitter from 'EventEmitter';
|
|||||||
export default class StyleRuleManager extends EventEmitter {
|
export default class StyleRuleManager extends EventEmitter {
|
||||||
constructor(styleConfiguration, openmct, callback, suppressSubscriptionOnEdit) {
|
constructor(styleConfiguration, openmct, callback, suppressSubscriptionOnEdit) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.refreshData = this.refreshData.bind(this);
|
this.refreshData = this.refreshData.bind(this);
|
||||||
@ -152,6 +153,7 @@ export default class StyleRuleManager extends EventEmitter {
|
|||||||
|
|
||||||
updateDomainObjectStyle() {
|
updateDomainObjectStyle() {
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
|
this.emit('updateStyles', this.currentStyle);
|
||||||
this.callback(Object.assign({}, this.currentStyle));
|
this.callback(Object.assign({}, this.currentStyle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-condition-h"
|
<div
|
||||||
|
class="c-condition-h"
|
||||||
:class="{ 'is-drag-target': draggingOver }"
|
:class="{ 'is-drag-target': draggingOver }"
|
||||||
@dragover.prevent
|
@dragover.prevent
|
||||||
@drop.prevent="dropCondition($event, conditionIndex)"
|
@drop.prevent="dropCondition($event, conditionIndex)"
|
||||||
@ -29,13 +30,15 @@
|
|||||||
@dragleave="dragLeave($event, conditionIndex)"
|
@dragleave="dragLeave($event, conditionIndex)"
|
||||||
>
|
>
|
||||||
<div class="c-condition-h__drop-target"></div>
|
<div class="c-condition-h__drop-target"></div>
|
||||||
<div v-if="isEditing"
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
:class="{'is-current': condition.id === currentConditionId}"
|
:class="{'is-current': condition.id === currentConditionId}"
|
||||||
class="c-condition c-condition--edit"
|
class="c-condition c-condition--edit"
|
||||||
>
|
>
|
||||||
<!-- Edit view -->
|
<!-- Edit view -->
|
||||||
<div class="c-condition__header">
|
<div class="c-condition__header">
|
||||||
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
|
<span
|
||||||
|
class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
|
||||||
title="Drag to reorder conditions"
|
title="Drag to reorder conditions"
|
||||||
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
|
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
|
||||||
:draggable="!condition.isDefault"
|
:draggable="!condition.isDefault"
|
||||||
@ -43,7 +46,8 @@
|
|||||||
@dragend="dragEnd"
|
@dragend="dragEnd"
|
||||||
></span>
|
></span>
|
||||||
|
|
||||||
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
|
<span
|
||||||
|
class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
|
||||||
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||||
@click="expanded = !expanded"
|
@click="expanded = !expanded"
|
||||||
></span>
|
></span>
|
||||||
@ -54,33 +58,38 @@
|
|||||||
Define criteria
|
Define criteria
|
||||||
</template>
|
</template>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<condition-description :show-label="false"
|
<condition-description
|
||||||
|
:show-label="false"
|
||||||
:condition="condition"
|
:condition="condition"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="c-condition__buttons">
|
<div class="c-condition__buttons">
|
||||||
<button v-if="!condition.isDefault"
|
<button
|
||||||
|
v-if="!condition.isDefault"
|
||||||
class="c-click-icon c-condition__duplicate-button icon-duplicate"
|
class="c-click-icon c-condition__duplicate-button icon-duplicate"
|
||||||
title="Duplicate this condition"
|
title="Duplicate this condition"
|
||||||
@click="cloneCondition"
|
@click="cloneCondition"
|
||||||
></button>
|
></button>
|
||||||
|
|
||||||
<button v-if="!condition.isDefault"
|
<button
|
||||||
|
v-if="!condition.isDefault"
|
||||||
class="c-click-icon c-condition__delete-button icon-trash"
|
class="c-click-icon c-condition__delete-button icon-trash"
|
||||||
title="Delete this condition"
|
title="Delete this condition"
|
||||||
@click="removeCondition"
|
@click="removeCondition"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expanded"
|
<div
|
||||||
|
v-if="expanded"
|
||||||
class="c-condition__definition c-cdef"
|
class="c-condition__definition c-cdef"
|
||||||
>
|
>
|
||||||
<span class="c-cdef__separator c-row-separator"></span>
|
<span class="c-cdef__separator c-row-separator"></span>
|
||||||
<span class="c-cdef__label">Condition Name</span>
|
<span class="c-cdef__label">Condition Name</span>
|
||||||
<span class="c-cdef__controls">
|
<span class="c-cdef__controls">
|
||||||
<input v-model="condition.configuration.name"
|
<input
|
||||||
|
v-model="condition.configuration.name"
|
||||||
class="t-condition-input__name"
|
class="t-condition-input__name"
|
||||||
type="text"
|
type="text"
|
||||||
@change="persist"
|
@change="persist"
|
||||||
@ -90,10 +99,12 @@
|
|||||||
<span class="c-cdef__label">Output</span>
|
<span class="c-cdef__label">Output</span>
|
||||||
<span class="c-cdef__controls">
|
<span class="c-cdef__controls">
|
||||||
<span class="c-cdef__control">
|
<span class="c-cdef__control">
|
||||||
<select v-model="selectedOutputSelection"
|
<select
|
||||||
|
v-model="selectedOutputSelection"
|
||||||
@change="setOutputValue"
|
@change="setOutputValue"
|
||||||
>
|
>
|
||||||
<option v-for="option in outputOptions"
|
<option
|
||||||
|
v-for="option in outputOptions"
|
||||||
:key="option"
|
:key="option"
|
||||||
:value="option"
|
:value="option"
|
||||||
>
|
>
|
||||||
@ -102,7 +113,8 @@
|
|||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span class="c-cdef__control">
|
<span class="c-cdef__control">
|
||||||
<input v-if="selectedOutputSelection === outputOptions[2]"
|
<input
|
||||||
|
v-if="selectedOutputSelection === outputOptions[2]"
|
||||||
v-model="condition.configuration.output"
|
v-model="condition.configuration.output"
|
||||||
class="t-condition-name-input"
|
class="t-condition-name-input"
|
||||||
type="text"
|
type="text"
|
||||||
@ -111,16 +123,19 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div v-if="!condition.isDefault"
|
<div
|
||||||
|
v-if="!condition.isDefault"
|
||||||
class="c-cdef__match-and-criteria"
|
class="c-cdef__match-and-criteria"
|
||||||
>
|
>
|
||||||
<span class="c-cdef__separator c-row-separator"></span>
|
<span class="c-cdef__separator c-row-separator"></span>
|
||||||
<span class="c-cdef__label">Match</span>
|
<span class="c-cdef__label">Match</span>
|
||||||
<span class="c-cdef__controls">
|
<span class="c-cdef__controls">
|
||||||
<select v-model="condition.configuration.trigger"
|
<select
|
||||||
|
v-model="condition.configuration.trigger"
|
||||||
@change="persist"
|
@change="persist"
|
||||||
>
|
>
|
||||||
<option v-for="option in triggers"
|
<option
|
||||||
|
v-for="option in triggers"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
> {{ option.label }}</option>
|
> {{ option.label }}</option>
|
||||||
@ -128,11 +143,13 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<template v-if="telemetry.length || condition.configuration.criteria.length">
|
<template v-if="telemetry.length || condition.configuration.criteria.length">
|
||||||
<div v-for="(criterion, index) in condition.configuration.criteria"
|
<div
|
||||||
|
v-for="(criterion, index) in condition.configuration.criteria"
|
||||||
:key="criterion.id"
|
:key="criterion.id"
|
||||||
class="c-cdef__criteria"
|
class="c-cdef__criteria"
|
||||||
>
|
>
|
||||||
<Criterion :telemetry="telemetry"
|
<Criterion
|
||||||
|
:telemetry="telemetry"
|
||||||
:criterion="criterion"
|
:criterion="criterion"
|
||||||
:index="index"
|
:index="index"
|
||||||
:trigger="condition.configuration.trigger"
|
:trigger="condition.configuration.trigger"
|
||||||
@ -140,11 +157,13 @@
|
|||||||
@persist="persist"
|
@persist="persist"
|
||||||
/>
|
/>
|
||||||
<div class="c-cdef__criteria__buttons">
|
<div class="c-cdef__criteria__buttons">
|
||||||
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
|
<button
|
||||||
|
class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
|
||||||
title="Duplicate this criteria"
|
title="Duplicate this criteria"
|
||||||
@click="cloneCriterion(index)"
|
@click="cloneCriterion(index)"
|
||||||
></button>
|
></button>
|
||||||
<button v-if="!(condition.configuration.criteria.length === 1)"
|
<button
|
||||||
|
v-if="!(condition.configuration.criteria.length === 1)"
|
||||||
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
|
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
|
||||||
title="Delete this criteria"
|
title="Delete this criteria"
|
||||||
@click="removeCriterion(index)"
|
@click="removeCriterion(index)"
|
||||||
@ -153,7 +172,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="c-cdef__separator c-row-separator"></div>
|
<div class="c-cdef__separator c-row-separator"></div>
|
||||||
<div class="c-cdef__controls"
|
<div
|
||||||
|
class="c-cdef__controls"
|
||||||
:disabled="!telemetry.length"
|
:disabled="!telemetry.length"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@ -166,7 +186,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else
|
<div
|
||||||
|
v-else
|
||||||
class="c-condition c-condition--browse"
|
class="c-condition c-condition--browse"
|
||||||
:class="{'is-current': condition.id === currentConditionId}"
|
:class="{'is-current': condition.id === currentConditionId}"
|
||||||
>
|
>
|
||||||
@ -180,7 +201,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-condition__summary">
|
<div class="c-condition__summary">
|
||||||
<condition-description :show-label="false"
|
<condition-description
|
||||||
|
:show-label="false"
|
||||||
:condition="condition"
|
:condition="condition"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section id="conditionCollection"
|
<section
|
||||||
|
id="conditionCollection"
|
||||||
:class="{ 'is-expanded': expanded }"
|
:class="{ 'is-expanded': expanded }"
|
||||||
>
|
>
|
||||||
<div class="c-cs__header c-section__header">
|
<div class="c-cs__header c-section__header">
|
||||||
@ -32,10 +33,12 @@
|
|||||||
></span>
|
></span>
|
||||||
<div class="c-cs__header-label c-section__label">Conditions</div>
|
<div class="c-cs__header-label c-section__label">Conditions</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expanded"
|
<div
|
||||||
|
v-if="expanded"
|
||||||
class="c-cs__content"
|
class="c-cs__content"
|
||||||
>
|
>
|
||||||
<div v-show="isEditing"
|
<div
|
||||||
|
v-show="isEditing"
|
||||||
class="hint"
|
class="hint"
|
||||||
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
|
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
|
||||||
>
|
>
|
||||||
@ -52,10 +55,12 @@
|
|||||||
<span class="c-cs-button__label">Add Condition</span>
|
<span class="c-cs-button__label">Add Condition</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="c-cs__conditions-h"
|
<div
|
||||||
|
class="c-cs__conditions-h"
|
||||||
:class="{ 'is-active-dragging': isDragging }"
|
:class="{ 'is-active-dragging': isDragging }"
|
||||||
>
|
>
|
||||||
<Condition v-for="(condition, index) in conditionCollection"
|
<Condition
|
||||||
|
v-for="(condition, index) in conditionCollection"
|
||||||
:key="condition.id"
|
:key="condition.id"
|
||||||
:condition="condition"
|
:condition="condition"
|
||||||
:current-condition-id="currentConditionId"
|
:current-condition-id="currentConditionId"
|
||||||
|
@ -22,17 +22,20 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-style__condition-desc">
|
<div class="c-style__condition-desc">
|
||||||
<span v-if="showLabel && condition"
|
<span
|
||||||
|
v-if="showLabel && condition"
|
||||||
class="c-style__condition-desc__name c-condition__name"
|
class="c-style__condition-desc__name c-condition__name"
|
||||||
>
|
>
|
||||||
{{ condition.configuration.name }}
|
{{ condition.configuration.name }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="!condition.isDefault"
|
<span
|
||||||
|
v-if="!condition.isDefault"
|
||||||
class="c-style__condition-desc__text"
|
class="c-style__condition-desc__text"
|
||||||
>
|
>
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else
|
<span
|
||||||
|
v-else
|
||||||
class="c-style__condition-desc__text"
|
class="c-style__condition-desc__text"
|
||||||
>
|
>
|
||||||
Match if no other condition is matched
|
Match if no other condition is matched
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="conditionErrors.length"
|
<div
|
||||||
|
v-if="conditionErrors.length"
|
||||||
class="c-condition__errors"
|
class="c-condition__errors"
|
||||||
>
|
>
|
||||||
<div v-for="(error, index) in conditionErrors"
|
<div
|
||||||
|
v-for="(error, index) in conditionErrors"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="u-alert u-alert--block u-alert--with-icon"
|
class="u-alert u-alert--block u-alert--with-icon"
|
||||||
>{{ error.message.errorText }} {{ error.additionalInfo }}
|
>{{ error.message.errorText }} {{ error.additionalInfo }}
|
||||||
|
@ -36,13 +36,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div class="c-cs__test-data-and-conditions-w">
|
<div class="c-cs__test-data-and-conditions-w">
|
||||||
<TestData class="c-cs__test-data"
|
<TestData
|
||||||
|
class="c-cs__test-data"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
:test-data="testData"
|
:test-data="testData"
|
||||||
:telemetry="telemetryObjs"
|
:telemetry="telemetryObjs"
|
||||||
@updateTestData="updateTestData"
|
@updateTestData="updateTestData"
|
||||||
/>
|
/>
|
||||||
<ConditionCollection class="c-cs__conditions"
|
<ConditionCollection
|
||||||
|
class="c-cs__conditions"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
:test-data="testData"
|
:test-data="testData"
|
||||||
@conditionSetResultUpdated="updateCurrentOutput"
|
@conditionSetResultUpdated="updateCurrentOutput"
|
||||||
|
@ -26,14 +26,16 @@
|
|||||||
<span class="c-cdef__label">{{ setRowLabel }}</span>
|
<span class="c-cdef__label">{{ setRowLabel }}</span>
|
||||||
<span class="c-cdef__controls">
|
<span class="c-cdef__controls">
|
||||||
<span class="c-cdef__control">
|
<span class="c-cdef__control">
|
||||||
<select ref="telemetrySelect"
|
<select
|
||||||
|
ref="telemetrySelect"
|
||||||
v-model="criterion.telemetry"
|
v-model="criterion.telemetry"
|
||||||
@change="updateMetadataOptions"
|
@change="updateMetadataOptions"
|
||||||
>
|
>
|
||||||
<option value="">- Select Telemetry -</option>
|
<option value="">- Select Telemetry -</option>
|
||||||
<option value="all">all telemetry</option>
|
<option value="all">all telemetry</option>
|
||||||
<option value="any">any telemetry</option>
|
<option value="any">any telemetry</option>
|
||||||
<option v-for="telemetryOption in telemetry"
|
<option
|
||||||
|
v-for="telemetryOption in telemetry"
|
||||||
:key="telemetryOption.identifier.key"
|
:key="telemetryOption.identifier.key"
|
||||||
:value="telemetryOption.identifier"
|
:value="telemetryOption.identifier"
|
||||||
>
|
>
|
||||||
@ -41,15 +43,18 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="criterion.telemetry"
|
<span
|
||||||
|
v-if="criterion.telemetry"
|
||||||
class="c-cdef__control"
|
class="c-cdef__control"
|
||||||
>
|
>
|
||||||
<select ref="metadataSelect"
|
<select
|
||||||
|
ref="metadataSelect"
|
||||||
v-model="criterion.metadata"
|
v-model="criterion.metadata"
|
||||||
@change="updateOperations"
|
@change="updateOperations"
|
||||||
>
|
>
|
||||||
<option value="">- Select Field -</option>
|
<option value="">- Select Field -</option>
|
||||||
<option v-for="option in telemetryMetadataOptions"
|
<option
|
||||||
|
v-for="option in telemetryMetadataOptions"
|
||||||
:key="option.key"
|
:key="option.key"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
@ -58,14 +63,17 @@
|
|||||||
<option value="dataReceived">any data received</option>
|
<option value="dataReceived">any data received</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="criterion.telemetry && criterion.metadata"
|
<span
|
||||||
|
v-if="criterion.telemetry && criterion.metadata"
|
||||||
class="c-cdef__control"
|
class="c-cdef__control"
|
||||||
>
|
>
|
||||||
<select v-model="criterion.operation"
|
<select
|
||||||
|
v-model="criterion.operation"
|
||||||
@change="updateInputVisibilityAndValues"
|
@change="updateInputVisibilityAndValues"
|
||||||
>
|
>
|
||||||
<option value="">- Select Comparison -</option>
|
<option value="">- Select Comparison -</option>
|
||||||
<option v-for="option in filteredOps"
|
<option
|
||||||
|
v-for="option in filteredOps"
|
||||||
:key="option.name"
|
:key="option.name"
|
||||||
:value="option.name"
|
:value="option.name"
|
||||||
>
|
>
|
||||||
@ -73,11 +81,13 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<template v-if="!enumerations.length">
|
<template v-if="!enumerations.length">
|
||||||
<span v-for="(item, inputIndex) in inputCount"
|
<span
|
||||||
|
v-for="(item, inputIndex) in inputCount"
|
||||||
:key="inputIndex"
|
:key="inputIndex"
|
||||||
class="c-cdef__control__inputs"
|
class="c-cdef__control__inputs"
|
||||||
>
|
>
|
||||||
<input v-model="criterion.input[inputIndex]"
|
<input
|
||||||
|
v-model="criterion.input[inputIndex]"
|
||||||
class="c-cdef__control__input"
|
class="c-cdef__control__input"
|
||||||
:type="setInputType"
|
:type="setInputType"
|
||||||
@change="persist"
|
@change="persist"
|
||||||
@ -87,13 +97,16 @@
|
|||||||
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
|
<span v-if="criterion.metadata === 'dataReceived'">seconds</span>
|
||||||
</template>
|
</template>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<span v-if="inputCount && criterion.operation"
|
<span
|
||||||
|
v-if="inputCount && criterion.operation"
|
||||||
class="c-cdef__control"
|
class="c-cdef__control"
|
||||||
>
|
>
|
||||||
<select v-model="criterion.input[0]"
|
<select
|
||||||
|
v-model="criterion.input[0]"
|
||||||
@change="persist"
|
@change="persist"
|
||||||
>
|
>
|
||||||
<option v-for="option in enumerations"
|
<option
|
||||||
|
v-for="option in enumerations"
|
||||||
:key="option.string"
|
:key="option.string"
|
||||||
:value="option.value.toString()"
|
:value="option.value.toString()"
|
||||||
>
|
>
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-show="isEditing"
|
<section
|
||||||
|
v-show="isEditing"
|
||||||
id="test-data"
|
id="test-data"
|
||||||
:class="{ 'is-expanded': expanded }"
|
:class="{ 'is-expanded': expanded }"
|
||||||
>
|
>
|
||||||
@ -33,10 +34,12 @@
|
|||||||
></span>
|
></span>
|
||||||
<div class="c-cs__header-label c-section__label">Test Data</div>
|
<div class="c-cs__header-label c-section__label">Test Data</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expanded"
|
<div
|
||||||
|
v-if="expanded"
|
||||||
class="c-cs__content"
|
class="c-cs__content"
|
||||||
>
|
>
|
||||||
<div class="c-cs__test-data__controls c-cdef__controls"
|
<div
|
||||||
|
class="c-cs__test-data__controls c-cdef__controls"
|
||||||
:disabled="!telemetry.length"
|
:disabled="!telemetry.length"
|
||||||
>
|
>
|
||||||
<label class="c-toggle-switch">
|
<label class="c-toggle-switch">
|
||||||
@ -50,18 +53,21 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-cs-tests">
|
<div class="c-cs-tests">
|
||||||
<span v-for="(testInput, tIndex) in testInputs"
|
<span
|
||||||
|
v-for="(testInput, tIndex) in testInputs"
|
||||||
:key="tIndex"
|
:key="tIndex"
|
||||||
class="c-test-datum c-cs-test"
|
class="c-test-datum c-cs-test"
|
||||||
>
|
>
|
||||||
<span class="c-cs-test__label">Set</span>
|
<span class="c-cs-test__label">Set</span>
|
||||||
<span class="c-cs-test__controls">
|
<span class="c-cs-test__controls">
|
||||||
<span class="c-cdef__control">
|
<span class="c-cdef__control">
|
||||||
<select v-model="testInput.telemetry"
|
<select
|
||||||
|
v-model="testInput.telemetry"
|
||||||
@change="updateMetadata(testInput)"
|
@change="updateMetadata(testInput)"
|
||||||
>
|
>
|
||||||
<option value="">- Select Telemetry -</option>
|
<option value="">- Select Telemetry -</option>
|
||||||
<option v-for="(telemetryOption, index) in telemetry"
|
<option
|
||||||
|
v-for="(telemetryOption, index) in telemetry"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="telemetryOption.identifier"
|
:value="telemetryOption.identifier"
|
||||||
>
|
>
|
||||||
@ -69,14 +75,17 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="testInput.telemetry"
|
<span
|
||||||
|
v-if="testInput.telemetry"
|
||||||
class="c-cdef__control"
|
class="c-cdef__control"
|
||||||
>
|
>
|
||||||
<select v-model="testInput.metadata"
|
<select
|
||||||
|
v-model="testInput.metadata"
|
||||||
@change="updateTestData"
|
@change="updateTestData"
|
||||||
>
|
>
|
||||||
<option value="">- Select Field -</option>
|
<option value="">- Select Field -</option>
|
||||||
<option v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
|
<option
|
||||||
|
v-for="(option, index) in telemetryMetadataOptions[getId(testInput.telemetry)]"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="option.key"
|
:value="option.key"
|
||||||
>
|
>
|
||||||
@ -84,10 +93,12 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="testInput.metadata"
|
<span
|
||||||
|
v-if="testInput.metadata"
|
||||||
class="c-cdef__control__inputs"
|
class="c-cdef__control__inputs"
|
||||||
>
|
>
|
||||||
<input v-model="testInput.value"
|
<input
|
||||||
|
v-model="testInput.value"
|
||||||
placeholder="Enter test input"
|
placeholder="Enter test input"
|
||||||
type="text"
|
type="text"
|
||||||
class="c-cdef__control__input"
|
class="c-cdef__control__input"
|
||||||
@ -96,11 +107,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="c-cs-test__buttons">
|
<div class="c-cs-test__buttons">
|
||||||
<button class="c-click-icon c-test-data__duplicate-button icon-duplicate"
|
<button
|
||||||
|
class="c-click-icon c-test-data__duplicate-button icon-duplicate"
|
||||||
title="Duplicate this test datum"
|
title="Duplicate this test datum"
|
||||||
@click="addTestInput(testInput)"
|
@click="addTestInput(testInput)"
|
||||||
></button>
|
></button>
|
||||||
<button class="c-click-icon c-test-data__delete-button icon-trash"
|
<button
|
||||||
|
class="c-click-icon c-test-data__delete-button icon-trash"
|
||||||
title="Delete this test datum"
|
title="Delete this test datum"
|
||||||
@click="removeTestInput(tIndex)"
|
@click="removeTestInput(tIndex)"
|
||||||
></button>
|
></button>
|
||||||
|
@ -23,41 +23,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-style has-local-controls c-toolbar">
|
<div class="c-style has-local-controls c-toolbar">
|
||||||
<div class="c-style__controls">
|
<div class="c-style__controls">
|
||||||
<div :class="[
|
<div
|
||||||
|
:class="[
|
||||||
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
|
{ 'is-style-invisible': styleItem.style && styleItem.style.isStyleInvisible },
|
||||||
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
|
{ 'c-style-thumb--mixed': mixedStyles.indexOf('backgroundColor') > -1 }
|
||||||
]"
|
]"
|
||||||
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
|
:style="[styleItem.style.imageUrl ? { backgroundImage:'url(' + styleItem.style.imageUrl + ')'} : itemStyle ]"
|
||||||
class="c-style-thumb"
|
class="c-style-thumb"
|
||||||
>
|
>
|
||||||
<span class="c-style-thumb__text"
|
<span
|
||||||
|
class="c-style-thumb__text"
|
||||||
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
|
:class="{ 'hide-nice': !hasProperty(styleItem.style.color) }"
|
||||||
>
|
>
|
||||||
ABC
|
ABC
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<toolbar-color-picker v-if="hasProperty(styleItem.style.border)"
|
<toolbar-color-picker
|
||||||
|
v-if="hasProperty(styleItem.style.border)"
|
||||||
class="c-style__toolbar-button--border-color u-menu-to--center"
|
class="c-style__toolbar-button--border-color u-menu-to--center"
|
||||||
:options="borderColorOption"
|
:options="borderColorOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
/>
|
/>
|
||||||
<toolbar-color-picker v-if="hasProperty(styleItem.style.backgroundColor)"
|
<toolbar-color-picker
|
||||||
|
v-if="hasProperty(styleItem.style.backgroundColor)"
|
||||||
class="c-style__toolbar-button--background-color u-menu-to--center"
|
class="c-style__toolbar-button--background-color u-menu-to--center"
|
||||||
:options="backgroundColorOption"
|
:options="backgroundColorOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
/>
|
/>
|
||||||
<toolbar-color-picker v-if="hasProperty(styleItem.style.color)"
|
<toolbar-color-picker
|
||||||
|
v-if="hasProperty(styleItem.style.color)"
|
||||||
class="c-style__toolbar-button--color u-menu-to--center"
|
class="c-style__toolbar-button--color u-menu-to--center"
|
||||||
:options="colorOption"
|
:options="colorOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
/>
|
/>
|
||||||
<toolbar-button v-if="hasProperty(styleItem.style.imageUrl)"
|
<toolbar-button
|
||||||
|
v-if="hasProperty(styleItem.style.imageUrl)"
|
||||||
class="c-style__toolbar-button--image-url"
|
class="c-style__toolbar-button--image-url"
|
||||||
:options="imageUrlOption"
|
:options="imageUrlOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
/>
|
/>
|
||||||
<toolbar-toggle-button v-if="hasProperty(styleItem.style.isStyleInvisible)"
|
<toolbar-toggle-button
|
||||||
|
v-if="hasProperty(styleItem.style.isStyleInvisible)"
|
||||||
class="c-style__toolbar-button--toggle-visible"
|
class="c-style__toolbar-button--toggle-visible"
|
||||||
:options="isStyleInvisibleOption"
|
:options="isStyleInvisibleOption"
|
||||||
@change="updateStyleValue"
|
@change="updateStyleValue"
|
||||||
@ -65,7 +72,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Save Styles -->
|
<!-- Save Styles -->
|
||||||
<toolbar-button v-if="canSaveStyle"
|
<toolbar-button
|
||||||
|
v-if="canSaveStyle"
|
||||||
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
|
class="c-style__toolbar-button--save c-local-controls--show-on-hover c-icon-button c-icon-button--major"
|
||||||
:options="saveOptions"
|
:options="saveOptions"
|
||||||
@click="saveItemStyle()"
|
@click="saveItemStyle()"
|
||||||
|
@ -22,7 +22,8 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-inspector__styles c-inspect-styles">
|
<div class="c-inspector__styles c-inspect-styles">
|
||||||
<div v-if="isStaticAndConditionalStyles"
|
<div
|
||||||
|
v-if="isStaticAndConditionalStyles"
|
||||||
class="c-inspect-styles__mixed-static-and-conditional u-alert u-alert--block u-alert--with-icon"
|
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.
|
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.
|
||||||
@ -37,10 +38,12 @@
|
|||||||
@set-font-property="setFontProperty"
|
@set-font-property="setFontProperty"
|
||||||
/>
|
/>
|
||||||
<div class="c-inspect-styles__content">
|
<div class="c-inspect-styles__content">
|
||||||
<div v-if="staticStyle"
|
<div
|
||||||
|
v-if="staticStyle"
|
||||||
class="c-inspect-styles__style"
|
class="c-inspect-styles__style"
|
||||||
>
|
>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<StyleEditor
|
||||||
|
class="c-inspect-styles__editor"
|
||||||
:style-item="staticStyle"
|
:style-item="staticStyle"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
:mixed-styles="mixedStyles"
|
:mixed-styles="mixedStyles"
|
||||||
@ -64,7 +67,8 @@
|
|||||||
Conditional Object Styles
|
Conditional Object Styles
|
||||||
</div>
|
</div>
|
||||||
<div class="c-inspect-styles__content c-inspect-styles__condition-set c-inspect-styles__elem">
|
<div class="c-inspect-styles__content c-inspect-styles__condition-set c-inspect-styles__elem">
|
||||||
<a v-if="conditionSetDomainObject"
|
<a
|
||||||
|
v-if="conditionSetDomainObject"
|
||||||
class="c-object-label"
|
class="c-object-label"
|
||||||
@click="navigateOrPreview"
|
@click="navigateOrPreview"
|
||||||
>
|
>
|
||||||
@ -80,14 +84,16 @@
|
|||||||
<span class="c-button__label">Change...</span>
|
<span class="c-button__label">Change...</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="c-click-icon icon-x"
|
<button
|
||||||
|
class="c-click-icon icon-x"
|
||||||
title="Remove conditional styles"
|
title="Remove conditional styles"
|
||||||
@click="removeConditionSet"
|
@click="removeConditionSet"
|
||||||
></button>
|
></button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isConditionWidget && allowEditing"
|
<div
|
||||||
|
v-if="isConditionWidget && allowEditing"
|
||||||
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
|
class="c-inspect-styles__elem c-inspect-styles__output-label-toggle"
|
||||||
>
|
>
|
||||||
<label class="c-toggle-switch">
|
<label class="c-toggle-switch">
|
||||||
@ -100,7 +106,8 @@
|
|||||||
<span class="c-toggle-switch__label">Use Condition Set output as label</span>
|
<span class="c-toggle-switch__label">Use Condition Set output as label</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isConditionWidget && !allowEditing"
|
<div
|
||||||
|
v-if="isConditionWidget && !allowEditing"
|
||||||
class="c-inspect-styles__elem"
|
class="c-inspect-styles__elem"
|
||||||
>
|
>
|
||||||
<span class="c-toggle-switch__label">Condition Set output as label:
|
<span class="c-toggle-switch__label">Condition Set output as label:
|
||||||
@ -114,22 +121,27 @@
|
|||||||
@set-font-property="setFontProperty"
|
@set-font-property="setFontProperty"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="conditionsLoaded"
|
<div
|
||||||
|
v-if="conditionsLoaded"
|
||||||
class="c-inspect-styles__conditions"
|
class="c-inspect-styles__conditions"
|
||||||
>
|
>
|
||||||
<div v-for="(conditionStyle, index) in conditionalStyles"
|
<div
|
||||||
|
v-for="(conditionStyle, index) in conditionalStyles"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="c-inspect-styles__condition"
|
class="c-inspect-styles__condition"
|
||||||
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
|
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
|
||||||
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
|
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
|
||||||
>
|
>
|
||||||
<condition-error :show-label="true"
|
<condition-error
|
||||||
|
:show-label="true"
|
||||||
:condition="getCondition(conditionStyle.conditionId)"
|
:condition="getCondition(conditionStyle.conditionId)"
|
||||||
/>
|
/>
|
||||||
<condition-description :show-label="true"
|
<condition-description
|
||||||
|
:show-label="true"
|
||||||
:condition="getCondition(conditionStyle.conditionId)"
|
:condition="getCondition(conditionStyle.conditionId)"
|
||||||
/>
|
/>
|
||||||
<StyleEditor class="c-inspect-styles__editor"
|
<StyleEditor
|
||||||
|
class="c-inspect-styles__editor"
|
||||||
:style-item="conditionStyle"
|
:style-item="conditionStyle"
|
||||||
:non-specific-font-properties="nonSpecificFontProperties"
|
:non-specific-font-properties="nonSpecificFontProperties"
|
||||||
:is-editing="allowEditing"
|
:is-editing="allowEditing"
|
||||||
@ -325,16 +337,7 @@ export default {
|
|||||||
return item && (item.type === type);
|
return item && (item.type === type);
|
||||||
},
|
},
|
||||||
canPersistObject(item) {
|
canPersistObject(item) {
|
||||||
// for now the only way to tell if an object can be persisted is if it is creatable.
|
return this.openmct.objects.isPersistable(item.identifier);
|
||||||
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) {
|
hasConditionalStyle(domainObject, layoutItem) {
|
||||||
const id = layoutItem ? layoutItem.id : undefined;
|
const id = layoutItem ? layoutItem.id : undefined;
|
||||||
|
@ -21,12 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component :is="urlDefined ? 'a' : 'span'"
|
<component
|
||||||
|
:is="urlDefined ? 'a' : 'span'"
|
||||||
class="c-condition-widget u-style-receiver js-style-receiver"
|
class="c-condition-widget u-style-receiver js-style-receiver"
|
||||||
:href="url"
|
:href="url"
|
||||||
>
|
>
|
||||||
<div class="c-condition-widget__label">
|
<div class="c-condition-widget__label">
|
||||||
{{ internalDomainObject.conditionalLabel || internalDomainObject.label }}
|
{{ label }}
|
||||||
</div>
|
</div>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
@ -38,10 +39,16 @@ export default {
|
|||||||
inject: ['openmct', 'domainObject'],
|
inject: ['openmct', 'domainObject'],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
internalDomainObject: this.domainObject
|
internalDomainObject: this.domainObject,
|
||||||
|
conditionalLabel: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
label() {
|
||||||
|
return this.conditionalLabel.length
|
||||||
|
? this.conditionalLabel
|
||||||
|
: this.internalDomainObject.label;
|
||||||
|
},
|
||||||
urlDefined() {
|
urlDefined() {
|
||||||
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
|
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
|
||||||
},
|
},
|
||||||
@ -51,13 +58,31 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
|
||||||
|
|
||||||
|
this.unobserve = this.openmct.styleManager.observe(this.internalDomainObject.identifier, this.observeStyleManagerChanges.bind(this));
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.unlisten) {
|
if (this.unlisten) {
|
||||||
this.unlisten();
|
this.unlisten();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.unobserve) {
|
||||||
|
this.openmct.styleManager.delete(this.internalDomainObject.identifier);
|
||||||
|
this.unobserve();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
observeStyleManagerChanges(styleManager) {
|
||||||
|
if (styleManager) {
|
||||||
|
this.styleManager = styleManager;
|
||||||
|
this.styleManager.on('updateStyles', this.updateConditionLabel);
|
||||||
|
} else {
|
||||||
|
this.styleManager.off('updateStyles', this.updateConditionLabel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateConditionLabel(styleObj = {}) {
|
||||||
|
this.conditionalLabel = styleObj.output || '';
|
||||||
|
},
|
||||||
updateInternalDomainObject(domainObject) {
|
updateInternalDomainObject(domainObject) {
|
||||||
this.internalDomainObject = domainObject;
|
this.internalDomainObject = domainObject;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
gridSize() {
|
gridSize() {
|
||||||
return this.domainObject.configuration.layoutGrid;
|
return this.domainObject.configuration.layoutGrid.map(Number);
|
||||||
},
|
},
|
||||||
layoutItems() {
|
layoutItems() {
|
||||||
return this.domainObject.configuration.items;
|
return this.domainObject.configuration.items;
|
||||||
|
@ -37,7 +37,8 @@
|
|||||||
:data-font="item.font"
|
:data-font="item.font"
|
||||||
@contextmenu.prevent="showContextMenu"
|
@contextmenu.prevent="showContextMenu"
|
||||||
>
|
>
|
||||||
<div class="is-status__indicator"
|
<div
|
||||||
|
class="is-status__indicator"
|
||||||
:title="`This item is ${status}`"
|
:title="`This item is ${status}`"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
@ -221,20 +222,18 @@ export default {
|
|||||||
.then(this.setObject);
|
.then(this.setObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.on("bounds", this.refreshData);
|
|
||||||
|
|
||||||
this.status = this.openmct.status.get(this.item.identifier);
|
this.status = this.openmct.status.get(this.item.identifier);
|
||||||
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.removeSubscription();
|
|
||||||
this.removeStatusListener();
|
this.removeStatusListener();
|
||||||
|
|
||||||
if (this.removeSelectable) {
|
if (this.removeSelectable) {
|
||||||
this.removeSelectable();
|
this.removeSelectable();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openmct.time.off("bounds", this.refreshData);
|
this.telemetryCollection.off('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.off('clear', this.refreshData);
|
||||||
|
|
||||||
if (this.mutablePromise) {
|
if (this.mutablePromise) {
|
||||||
this.mutablePromise.then(() => {
|
this.mutablePromise.then(() => {
|
||||||
@ -252,34 +251,9 @@ export default {
|
|||||||
|
|
||||||
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`;
|
return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`;
|
||||||
},
|
},
|
||||||
requestHistoricalData() {
|
setLatestValues(data) {
|
||||||
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.latestDatum = data[data.length - 1];
|
||||||
this.updateView();
|
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() {
|
updateView() {
|
||||||
if (!this.updatingView) {
|
if (!this.updatingView) {
|
||||||
@ -290,17 +264,10 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeSubscription() {
|
|
||||||
if (this.subscription) {
|
|
||||||
this.subscription();
|
|
||||||
this.subscription = undefined;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
refreshData(bounds, isTick) {
|
refreshData(bounds, isTick) {
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
this.latestDatum = undefined;
|
this.latestDatum = undefined;
|
||||||
this.updateView();
|
this.updateView();
|
||||||
this.requestHistoricalData(this.domainObject);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setObject(domainObject) {
|
setObject(domainObject) {
|
||||||
@ -314,8 +281,13 @@ export default {
|
|||||||
const valueMetadata = this.metadata.value(this.item.value);
|
const valueMetadata = this.metadata.value(this.item.value);
|
||||||
this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format);
|
this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format);
|
||||||
|
|
||||||
this.requestHistoricalData();
|
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {
|
||||||
this.subscribeToObject();
|
size: 1,
|
||||||
|
strategy: 'latest'
|
||||||
|
});
|
||||||
|
this.telemetryCollection.on('add', this.setLatestValues);
|
||||||
|
this.telemetryCollection.on('clear', this.refreshData);
|
||||||
|
this.telemetryCollection.load();
|
||||||
|
|
||||||
this.currentObjectPath = this.objectPath.slice();
|
this.currentObjectPath = this.objectPath.slice();
|
||||||
this.currentObjectPath.unshift(this.domainObject);
|
this.currentObjectPath.unshift(this.domainObject);
|
||||||
|
@ -97,13 +97,16 @@ export default class DuplicateAction {
|
|||||||
|
|
||||||
validate(currentParent) {
|
validate(currentParent) {
|
||||||
return (data) => {
|
return (data) => {
|
||||||
const parentCandidatePath = data.value;
|
const parentCandidate = data.value[0];
|
||||||
const parentCandidate = parentCandidatePath[0];
|
|
||||||
|
|
||||||
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
let currentParentKeystring = this.openmct.objects.makeKeyString(currentParent.identifier);
|
||||||
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
let parentCandidateKeystring = this.openmct.objects.makeKeyString(parentCandidate.identifier);
|
||||||
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
let objectKeystring = this.openmct.objects.makeKeyString(this.object.identifier);
|
||||||
|
|
||||||
|
if (!this.openmct.objects.isPersistable(parentCandidate.identifier)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!parentCandidateKeystring || !currentParentKeystring) {
|
if (!parentCandidateKeystring || !currentParentKeystring) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -122,13 +125,14 @@ export default class DuplicateAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
appliesTo(objectPath) {
|
appliesTo(objectPath) {
|
||||||
let parent = objectPath[1];
|
const parent = objectPath[1];
|
||||||
let parentType = parent && this.openmct.types.get(parent.type);
|
const parentType = parent && this.openmct.types.get(parent.type);
|
||||||
let child = objectPath[0];
|
const child = objectPath[0];
|
||||||
let childType = child && this.openmct.types.get(child.type);
|
const childType = child && this.openmct.types.get(child.type);
|
||||||
let locked = child.locked ? child.locked : parent && parent.locked;
|
const locked = child.locked ? child.locked : parent && parent.locked;
|
||||||
|
const isPersistable = this.openmct.objects.isPersistable(child.identifier);
|
||||||
|
|
||||||
if (locked) {
|
if (locked || !isPersistable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export default class ExportAsJSONAction {
|
|||||||
appliesTo(objectPath) {
|
appliesTo(objectPath) {
|
||||||
let domainObject = objectPath[0];
|
let domainObject = objectPath[0];
|
||||||
|
|
||||||
return this._isCreatable(domainObject);
|
return this._isCreatableAndPersistable(domainObject);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -80,10 +80,11 @@ export default class ExportAsJSONAction {
|
|||||||
* @param {object} domainObject
|
* @param {object} domainObject
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
_isCreatable(domainObject) {
|
_isCreatableAndPersistable(domainObject) {
|
||||||
const type = this.openmct.types.get(domainObject.type);
|
const type = this.openmct.types.get(domainObject.type);
|
||||||
|
const isPersistable = this.openmct.objects.isPersistable(domainObject.identifier);
|
||||||
|
|
||||||
return type && type.definition.creatable;
|
return type && type.definition.creatable && isPersistable;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -170,7 +171,7 @@ export default class ExportAsJSONAction {
|
|||||||
.then((children) => {
|
.then((children) => {
|
||||||
children.forEach((child, index) => {
|
children.forEach((child, index) => {
|
||||||
// Only export if object is creatable
|
// Only export if object is creatable
|
||||||
if (this._isCreatable(child)) {
|
if (this._isCreatableAndPersistable(child)) {
|
||||||
// Prevents infinite export of self-contained objs
|
// Prevents infinite export of self-contained objs
|
||||||
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
|
||||||
// If object is a link to something absent from
|
// If object is a link to something absent from
|
||||||
|
@ -27,6 +27,10 @@ describe('Export as JSON plugin', () => {
|
|||||||
|
|
||||||
it('ExportAsJSONAction applies to folder', () => {
|
it('ExportAsJSONAction applies to folder', () => {
|
||||||
domainObject = {
|
domainObject = {
|
||||||
|
identifier: {
|
||||||
|
key: 'export-testing',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
composition: [],
|
composition: [],
|
||||||
location: 'mine',
|
location: 'mine',
|
||||||
modified: 1640115501237,
|
modified: 1640115501237,
|
||||||
@ -40,6 +44,10 @@ describe('Export as JSON plugin', () => {
|
|||||||
|
|
||||||
it('ExportAsJSONAction applies to telemetry.plot.overlay', () => {
|
it('ExportAsJSONAction applies to telemetry.plot.overlay', () => {
|
||||||
domainObject = {
|
domainObject = {
|
||||||
|
identifier: {
|
||||||
|
key: 'export-testing',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
composition: [],
|
composition: [],
|
||||||
location: 'mine',
|
location: 'mine',
|
||||||
modified: 1640115501237,
|
modified: 1640115501237,
|
||||||
@ -53,6 +61,10 @@ describe('Export as JSON plugin', () => {
|
|||||||
|
|
||||||
it('ExportAsJSONAction applies to telemetry.plot.stacked', () => {
|
it('ExportAsJSONAction applies to telemetry.plot.stacked', () => {
|
||||||
domainObject = {
|
domainObject = {
|
||||||
|
identifier: {
|
||||||
|
key: 'export-testing',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
composition: [],
|
composition: [],
|
||||||
location: 'mine',
|
location: 'mine',
|
||||||
modified: 1640115501237,
|
modified: 1640115501237,
|
||||||
@ -64,16 +76,24 @@ describe('Export as JSON plugin', () => {
|
|||||||
expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true);
|
expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ExportAsJSONAction applies does not applies to non-creatable objects', () => {
|
it('ExportAsJSONAction does not applie to non-persistable objects', () => {
|
||||||
domainObject = {
|
domainObject = {
|
||||||
|
identifier: {
|
||||||
|
key: 'export-testing',
|
||||||
|
namespace: ''
|
||||||
|
},
|
||||||
composition: [],
|
composition: [],
|
||||||
location: 'mine',
|
location: 'mine',
|
||||||
modified: 1640115501237,
|
modified: 1640115501237,
|
||||||
name: 'Non Editable Folder',
|
name: 'Non Editable Folder',
|
||||||
persisted: 1640115501237,
|
persisted: 1640115501237,
|
||||||
type: 'noneditable.folder'
|
type: 'folder'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
spyOn(openmct.objects, 'getProvider').and.callFake(() => {
|
||||||
|
return { get: () => domainObject };
|
||||||
|
});
|
||||||
|
|
||||||
expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(false);
|
expect(exportAsJSONAction.appliesTo([domainObject])).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<drop-hint
|
<drop-hint
|
||||||
:key="i"
|
:key="'hint-' + i"
|
||||||
class="c-fl-frame__drop-hint"
|
class="c-fl-frame__drop-hint"
|
||||||
:index="i"
|
:index="i"
|
||||||
:allow-drop="allowDrop"
|
:allow-drop="allowDrop"
|
||||||
@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
<resize-handle
|
<resize-handle
|
||||||
v-if="(i !== frames.length - 1)"
|
v-if="(i !== frames.length - 1)"
|
||||||
:key="i"
|
:key="'handle-' + i"
|
||||||
:index="i"
|
:index="i"
|
||||||
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
|
:orientation="rowsLayout ? 'horizontal' : 'vertical'"
|
||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user