mirror of
https://github.com/nasa/openmct.git
synced 2025-07-01 21:01:11 +00:00
Compare commits
26 Commits
Updates-to
...
temp-sourc
Author | SHA1 | Date | |
---|---|---|---|
1a9c845f84 | |||
c46849b166 | |||
6c71fa01f5 | |||
c56d458ecb | |||
f74a35f45a | |||
dfa2e1ef1e | |||
fa4c58a7cb | |||
6c642281e7 | |||
68e46e45c7 | |||
8cd87ff9d1 | |||
d9ac0182c3 | |||
7bb108c36b | |||
77804cff75 | |||
2d73296b36 | |||
405418b9d5 | |||
f999b9e12b | |||
664ba399ea | |||
c6078a234a | |||
17c16eba50 | |||
9f9c69ee68 | |||
037886aa01 | |||
48916564e4 | |||
1ca5271c3e | |||
6521b888d6 | |||
85fce3c456 | |||
8d577a8958 |
@ -23,7 +23,7 @@ commands:
|
|||||||
- node/install:
|
- node/install:
|
||||||
install-npm: true
|
install-npm: true
|
||||||
node-version: << parameters.node-version >>
|
node-version: << parameters.node-version >>
|
||||||
- run: npm install
|
- run: npm install --prefer-offline --no-audit --progress=false
|
||||||
restore_cache_cmd:
|
restore_cache_cmd:
|
||||||
description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache"
|
description: "Custom command for restoring cache with the ability to bust cache. When BUST_CACHE is set to true, jobs will not restore cache"
|
||||||
parameters:
|
parameters:
|
||||||
@ -128,16 +128,30 @@ jobs:
|
|||||||
suite:
|
suite:
|
||||||
type: string
|
type: string
|
||||||
executor: pw-focal-development
|
executor: pw-focal-development
|
||||||
|
parallelism: 4
|
||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: <<parameters.node-version>>
|
node-version: <<parameters.node-version>>
|
||||||
- run: npx playwright install
|
- run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npm run test:e2e:<<parameters.suite>> -- --shard=${SHARD}/${CIRCLE_NODE_TOTAL}
|
||||||
- run: npm run test:e2e:<<parameters.suite>>
|
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: test-results/results.xml
|
path: test-results/results.xml
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: test-results
|
path: test-results
|
||||||
- generate_and_store_version_and_filesystem_artifacts
|
- generate_and_store_version_and_filesystem_artifacts
|
||||||
|
perf-test:
|
||||||
|
parameters:
|
||||||
|
node-version:
|
||||||
|
type: string
|
||||||
|
executor: pw-focal-development
|
||||||
|
steps:
|
||||||
|
- build_and_install:
|
||||||
|
node-version: <<parameters.node-version>>
|
||||||
|
- run: npm run test:perf
|
||||||
|
- store_test_results:
|
||||||
|
path: test-results/results.xml
|
||||||
|
- store_artifacts:
|
||||||
|
path: test-results
|
||||||
|
- generate_and_store_version_and_filesystem_artifacts
|
||||||
workflows:
|
workflows:
|
||||||
overall-circleci-commit-status: #These jobs run on every commit
|
overall-circleci-commit-status: #These jobs run on every commit
|
||||||
jobs:
|
jobs:
|
||||||
@ -150,10 +164,6 @@ workflows:
|
|||||||
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:
|
- unit-test:
|
||||||
name: node18-chrome
|
name: node18-chrome
|
||||||
node-version: "18"
|
node-version: "18"
|
||||||
@ -162,6 +172,8 @@ workflows:
|
|||||||
name: e2e-ci
|
name: e2e-ci
|
||||||
node-version: lts/gallium
|
node-version: lts/gallium
|
||||||
suite: ci
|
suite: ci
|
||||||
|
- perf-test:
|
||||||
|
node-version: lts/gallium
|
||||||
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:
|
||||||
|
@ -29,6 +29,7 @@ module.exports = {
|
|||||||
"you-dont-need-lodash-underscore/omit": "off",
|
"you-dont-need-lodash-underscore/omit": "off",
|
||||||
"you-dont-need-lodash-underscore/throttle": "off",
|
"you-dont-need-lodash-underscore/throttle": "off",
|
||||||
"you-dont-need-lodash-underscore/flatten": "off",
|
"you-dont-need-lodash-underscore/flatten": "off",
|
||||||
|
"you-dont-need-lodash-underscore/get": "off",
|
||||||
"no-bitwise": "error",
|
"no-bitwise": "error",
|
||||||
"curly": "error",
|
"curly": "error",
|
||||||
"eqeqeq": "error",
|
"eqeqeq": "error",
|
||||||
|
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -27,7 +27,7 @@ assignees: ''
|
|||||||
|
|
||||||
#### Environment
|
#### Environment
|
||||||
<!--- If encountered on local machine, execute the following:
|
<!--- If encountered on local machine, execute the following:
|
||||||
<!--- npx envinfo --system --browsers --npmPackages --binaries --languages --markdown -->
|
<!--- npx envinfo --system --browsers --npmPackages --binaries --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:
|
||||||
@ -41,6 +41,7 @@ assignees: ''
|
|||||||
- [ ] Does this impact a critical component?
|
- [ ] Does this impact a critical component?
|
||||||
- [ ] Is this just a visual bug with no functional impact?
|
- [ ] Is this just a visual bug with no functional impact?
|
||||||
- [ ] Does this block the execution of e2e tests?
|
- [ ] Does this block the execution of e2e tests?
|
||||||
|
- [ ] Does this have an impact on Performance?
|
||||||
|
|
||||||
#### Additional Information
|
#### Additional Information
|
||||||
<!--- Include any screenshots, gifs, or logs which will expedite triage -->
|
<!--- Include any screenshots, gifs, or logs which will expedite triage -->
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"extends": ["plugin:playwright/playwright-test"]
|
"extends": ["plugin:playwright/playwright-test"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["tests/visual/*.spec.js"],
|
||||||
|
"rules": {
|
||||||
|
"playwright/no-wait-for-timeout": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { devices } = require('@playwright/test');
|
const { devices } = require('@playwright/test');
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 1,
|
retries: 1,
|
||||||
testDir: 'tests',
|
testDir: 'tests',
|
||||||
|
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'npm run start',
|
||||||
@ -15,14 +17,15 @@ const config = {
|
|||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: !process.env.CI
|
||||||
},
|
},
|
||||||
|
maxFailures: process.env.CI ? 5 : undefined, //Limits failures to 5 to reduce CI Waste
|
||||||
workers: 2, //Limit to 2 for CircleCI Agent
|
workers: 2, //Limit to 2 for CircleCI Agent
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: true,
|
headless: true,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'on',
|
screenshot: 'only-on-failure',
|
||||||
trace: 'on',
|
trace: 'on-first-retry',
|
||||||
video: 'on'
|
video: 'on-first-retry'
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
@ -52,8 +55,11 @@ const config = {
|
|||||||
],
|
],
|
||||||
reporter: [
|
reporter: [
|
||||||
['list'],
|
['list'],
|
||||||
|
['html', {
|
||||||
|
open: 'never',
|
||||||
|
outputFolder: '../test-results/html/'
|
||||||
|
}],
|
||||||
['junit', { outputFile: 'test-results/results.xml' }],
|
['junit', { outputFile: 'test-results/results.xml' }],
|
||||||
['allure-playwright'],
|
|
||||||
['github']
|
['github']
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { devices } = require('@playwright/test');
|
const { devices } = require('@playwright/test');
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 0,
|
retries: 0,
|
||||||
testDir: 'tests',
|
testDir: 'tests',
|
||||||
|
testIgnore: '**/*.perf.spec.js',
|
||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'npm run start',
|
||||||
@ -21,9 +23,9 @@ const config = {
|
|||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: false,
|
headless: false,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'on',
|
screenshot: 'only-on-failure',
|
||||||
trace: 'on',
|
trace: 'retain-on-failure',
|
||||||
video: 'on'
|
video: 'retain-on-failure'
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
@ -53,7 +55,10 @@ const config = {
|
|||||||
],
|
],
|
||||||
reporter: [
|
reporter: [
|
||||||
['list'],
|
['list'],
|
||||||
['allure-playwright']
|
['html', {
|
||||||
|
open: 'on-failure',
|
||||||
|
outputFolder: '../test-results'
|
||||||
|
}]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
41
e2e/playwright-performance.config.js
Normal file
41
e2e/playwright-performance.config.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
|
// playwright.config.js
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
|
const config = {
|
||||||
|
retries: 0,
|
||||||
|
testDir: 'tests/performance/',
|
||||||
|
timeout: 30 * 1000,
|
||||||
|
workers: 1, //Only run in serial with 1 worker
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run start',
|
||||||
|
port: 8080,
|
||||||
|
timeout: 200 * 1000,
|
||||||
|
reuseExistingServer: !process.env.CI
|
||||||
|
},
|
||||||
|
use: {
|
||||||
|
browserName: "chromium",
|
||||||
|
baseURL: 'http://localhost:8080/',
|
||||||
|
headless: Boolean(process.env.CI), //Only if running locally
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
screenshot: 'off',
|
||||||
|
trace: 'off',
|
||||||
|
video: 'off'
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chrome',
|
||||||
|
use: {
|
||||||
|
browserName: 'chromium'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
reporter: [
|
||||||
|
['list'],
|
||||||
|
['junit', { outputFile: 'test-results/results.xml' }],
|
||||||
|
['json', { outputFile: 'test-results/results.json' }]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 0,
|
retries: 0, // visual tests should never retry due to snapshot comparison errors
|
||||||
testDir: 'tests',
|
testDir: 'tests/visual',
|
||||||
timeout: 90 * 1000,
|
timeout: 90 * 1000,
|
||||||
workers: 1,
|
workers: 1, // visual tests should never run in parallel due to test pollution
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'npm run start',
|
||||||
port: 8080,
|
port: 8080,
|
||||||
@ -17,7 +17,7 @@ const config = {
|
|||||||
use: {
|
use: {
|
||||||
browserName: "chromium",
|
browserName: "chromium",
|
||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: true,
|
headless: true, // this needs to remain headless to avoid visual changes due to GPU
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'on',
|
screenshot: 'on',
|
||||||
trace: 'off',
|
trace: 'off',
|
||||||
@ -25,8 +25,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
reporter: [
|
reporter: [
|
||||||
['list'],
|
['list'],
|
||||||
['junit', { outputFile: 'test-results/results.xml' }],
|
['junit', { outputFile: 'test-results/results.xml' }]
|
||||||
['allure-playwright']
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
1
e2e/test-data/PerformanceDisplayLayout.json
Normal file
1
e2e/test-data/PerformanceDisplayLayout.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"openmct":{"21338566-d472-4377-aed1-21b79272c8de":{"identifier":{"key":"21338566-d472-4377-aed1-21b79272c8de","namespace":""},"name":"Performance Display Layout","type":"layout","composition":[{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""}],"configuration":{"items":[{"width":32,"height":18,"x":1,"y":1,"identifier":{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"5aeb5a71-3149-41ed-9d8a-d34b0a18b053"}],"layoutGrid":[10,10]},"modified":1652228997384,"location":"mine","persisted":1652228997384},"644c2e47-2903-475f-8a4a-6be1588ee02f":{"identifier":{"key":"644c2e47-2903-475f-8a4a-6be1588ee02f","namespace":""},"name":"Performance Example Imagery","type":"example.imagery","configuration":{"imageLocation":"","imageLoadDelayInMilliSeconds":20000,"imageSamples":[]},"telemetry":{"values":[{"name":"Name","key":"name"},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2}},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1}},{"name":"Image","key":"url","format":"image","hints":{"image":1}},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1}}]},"modified":1652228997375,"location":"21338566-d472-4377-aed1-21b79272c8de","persisted":1652228997375}},"rootId":"21338566-d472-4377-aed1-21b79272c8de"}
|
1
e2e/test-data/PerformanceNotebook.json
Normal file
1
e2e/test-data/PerformanceNotebook.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"openmct":{"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d":{"identifier":{"key":"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d","namespace":""},"name":"Performance Notebook","type":"notebook","configuration":{"defaultSort":"oldest","entries":{"3e31c412-33ba-4757-8ade-e9821f6ba321":{"8c8f6035-631c-45af-8c24-786c60295335":[{"id":"entry-1652815305457","createdOn":1652815305457,"createdBy":"","text":"Existing Entry 1","embeds":[]},{"id":"entry-1652815313465","createdOn":1652815313465,"createdBy":"","text":"Existing Entry 2","embeds":[]},{"id":"entry-1652815399955","createdOn":1652815399955,"createdBy":"","text":"Existing Entry 3","embeds":[]}]}},"imageMigrationVer":"v1","pageTitle":"Page","sections":[{"id":"3e31c412-33ba-4757-8ade-e9821f6ba321","isDefault":false,"isSelected":false,"name":"Section1","pages":[{"id":"8c8f6035-631c-45af-8c24-786c60295335","isDefault":false,"isSelected":false,"name":"Page1","pageTitle":"Page"},{"id":"36555942-c9aa-439c-bbdb-0aaf50db50f5","isDefault":false,"isSelected":false,"name":"Page2","pageTitle":"Page"}],"sectionTitle":"Section"},{"id":"dab0bd1d-2c5a-405c-987f-107123d6189a","isDefault":false,"isSelected":true,"name":"Section2","pages":[{"id":"f625a86a-cb99-4898-8082-80543c8de534","isDefault":false,"isSelected":false,"name":"Page1","pageTitle":"Page"},{"id":"e77ef810-f785-42a7-942e-07e999b79c59","isDefault":false,"isSelected":true,"name":"Page2","pageTitle":"Page"}],"sectionTitle":"Section"}],"sectionTitle":"Section","type":"General","showTime":"0"},"modified":1652815915219,"location":"mine","persisted":1652815915222}},"rootId":"6d2fa9fd-f2aa-461a-a1e1-164ac44bec9d"}
|
@ -43,8 +43,6 @@ test.describe('forms set', () => {
|
|||||||
await page.fill('text=Properties Title Notes >> input[type="text"]', '');
|
await page.fill('text=Properties Title Notes >> input[type="text"]', '');
|
||||||
// Press Tab
|
// Press Tab
|
||||||
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
|
await page.press('text=Properties Title Notes >> input[type="text"]', 'Tab');
|
||||||
// Click text=OK Cancel
|
|
||||||
await page.click('text=OK', { force: true });
|
|
||||||
|
|
||||||
const okButton = page.locator('text=OK');
|
const okButton = page.locator('text=OK');
|
||||||
|
|
||||||
|
@ -58,6 +58,6 @@ test.describe('Branding tests', () => {
|
|||||||
page.waitForEvent('popup'),
|
page.waitForEvent('popup'),
|
||||||
page.locator('text=click here for third party licensing information').click()
|
page.locator('text=click here for third party licensing information').click()
|
||||||
]);
|
]);
|
||||||
expect(page2.waitForURL('**\/licenses**')).toBeTruthy();
|
expect(page2.waitForURL('**/licenses**')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -39,6 +39,10 @@ test.describe('Move item tests', () => {
|
|||||||
|
|
||||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1);
|
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder1);
|
||||||
|
|
||||||
|
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||||
|
await page.click('form[name="mctForm"] a:has-text("My Items")');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=OK').click(),
|
page.locator('text=OK').click(),
|
||||||
@ -54,6 +58,10 @@ test.describe('Move item tests', () => {
|
|||||||
await page.locator('li.icon-folder').click();
|
await page.locator('li.icon-folder').click();
|
||||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2);
|
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(folder2);
|
||||||
|
|
||||||
|
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||||
|
await page.click('form[name="mctForm"] a:has-text("My Items")');
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=OK').click(),
|
page.locator('text=OK').click(),
|
||||||
@ -72,10 +80,8 @@ test.describe('Move item tests', () => {
|
|||||||
});
|
});
|
||||||
await page.locator('li.icon-move').click();
|
await page.locator('li.icon-move').click();
|
||||||
await page.locator('form[name="mctForm"] >> text=My Items').click();
|
await page.locator('form[name="mctForm"] >> text=My Items').click();
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
await page.locator('text=OK').click();
|
||||||
page.locator('text=OK').click()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Expect that Folder 2 is in My Items, the root folder
|
// Expect that Folder 2 is in My Items, the root folder
|
||||||
expect(page.locator(`text=My Items >> nth=0:has(text=${folder2})`)).toBeTruthy();
|
expect(page.locator(`text=My Items >> nth=0:has(text=${folder2})`)).toBeTruthy();
|
||||||
@ -90,10 +96,11 @@ test.describe('Move item tests', () => {
|
|||||||
await page.locator('li:has-text("Telemetry Table")').click();
|
await page.locator('li:has-text("Telemetry Table")').click();
|
||||||
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
await page.locator('text=Properties Title Notes >> input[type="text"]').click();
|
||||||
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
|
await page.locator('text=Properties Title Notes >> input[type="text"]').fill(telemetryTable);
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||||
page.locator('text=OK').click()
|
await page.click('form[name="mctForm"] a:has-text("My Items")');
|
||||||
]);
|
|
||||||
|
await page.locator('text=OK').click();
|
||||||
|
|
||||||
// Finish editing and save Telemetry Table
|
// Finish editing and save Telemetry Table
|
||||||
await page.locator('.c-button--menu.c-button--major.icon-save').click();
|
await page.locator('.c-button--menu.c-button--major.icon-save').click();
|
||||||
@ -114,10 +121,7 @@ test.describe('Move item tests', () => {
|
|||||||
|
|
||||||
// Continue test regardless of assertion and create it in My Items
|
// Continue test regardless of assertion and create it in My Items
|
||||||
await page.locator('form[name="mctForm"] >> text=My Items').click();
|
await page.locator('form[name="mctForm"] >> text=My Items').click();
|
||||||
await Promise.all([
|
await page.locator('text=OK').click();
|
||||||
page.waitForNavigation(),
|
|
||||||
page.locator('text=OK').click()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Open My Items
|
// Open My Items
|
||||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||||
|
177
e2e/tests/performance/imagery.perf.spec.js
Normal file
177
e2e/tests/performance/imagery.perf.spec.js
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 performance tests to ensure that testability of performance
|
||||||
|
is not broken upstream on Open MCT. Any assumptions made downstream will be tested here
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- Update resolution of performance config
|
||||||
|
- Add Performance Observer on init to push all performance marks
|
||||||
|
- Move client CDP connection to before or to a fixture
|
||||||
|
-
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
|
||||||
|
|
||||||
|
test.describe('Performance tests', () => {
|
||||||
|
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click a:has-text("My Items")
|
||||||
|
await page.locator('a:has-text("My Items")').click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click text=Import from JSON
|
||||||
|
await page.locator('text=Import from JSON').click();
|
||||||
|
|
||||||
|
// Upload Performance Display Layout.json
|
||||||
|
await page.setInputFiles('#fileElem', filePath);
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await page.locator('text=OK').click();
|
||||||
|
|
||||||
|
await expect(page.locator('a:has-text("Performance Display Layout Display Layout")')).toBeVisible();
|
||||||
|
|
||||||
|
//Create a Chrome Performance Timeline trace to store as a test artifact
|
||||||
|
console.log("\n==== Devtools: startTracing ====\n");
|
||||||
|
await browser.startTracing(page, {
|
||||||
|
path: `${testInfo.outputPath()}-trace.json`,
|
||||||
|
screenshots: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test.afterEach(async ({ page, browser}) => {
|
||||||
|
console.log("\n==== Devtools: stopTracing ====\n");
|
||||||
|
await browser.stopTracing();
|
||||||
|
|
||||||
|
/* Measurement Section
|
||||||
|
/ The following section includes a block of performance measurements.
|
||||||
|
*/
|
||||||
|
//Get time difference between viewlarge actionability and evaluate time
|
||||||
|
await page.evaluate(() => (window.performance.measure("machine-time-difference", "viewlarge.start", "viewLarge.start.test")));
|
||||||
|
|
||||||
|
//Get StartTime
|
||||||
|
const startTime = await page.evaluate(() => window.performance.timing.navigationStart);
|
||||||
|
console.log('window.performance.timing.navigationStart', startTime);
|
||||||
|
|
||||||
|
//Get All Performance Marks
|
||||||
|
const getAllMarksJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("mark")));
|
||||||
|
const getAllMarks = JSON.parse(getAllMarksJson);
|
||||||
|
console.log('window.performance.getEntriesByType("mark")', getAllMarks);
|
||||||
|
|
||||||
|
//Get All Performance Measures
|
||||||
|
const getAllMeasuresJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("measure")));
|
||||||
|
const getAllMeasures = JSON.parse(getAllMeasuresJson);
|
||||||
|
console.log('window.performance.getEntriesByType("measure")', getAllMeasures);
|
||||||
|
|
||||||
|
});
|
||||||
|
/* The following test will navigate to a previously created Performance Display Layout and measure the
|
||||||
|
/ following metrics:
|
||||||
|
/ - ElementResourceTiming
|
||||||
|
/ - Interaction Timing
|
||||||
|
*/
|
||||||
|
test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => {
|
||||||
|
const client = await page.context().newCDPSession(page);
|
||||||
|
// Tell the DevTools session to record performance metrics
|
||||||
|
// https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics
|
||||||
|
await client.send('Performance.enable');
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// Search Available after Launch
|
||||||
|
await page.locator('input[type="search"]').click();
|
||||||
|
await page.evaluate(() => window.performance.mark("search-available"));
|
||||||
|
// Fill Search input
|
||||||
|
await page.locator('input[type="search"]').fill('Performance Display Layout');
|
||||||
|
await page.evaluate(() => window.performance.mark("search-entered"));
|
||||||
|
//Search Result Appears and is clicked
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('a:has-text("Performance Display Layout")').first().click(),
|
||||||
|
page.evaluate(() => window.performance.mark("click-search-result"))
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Time to Example Imagery Frame loads within Display Layout
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'});
|
||||||
|
//Time to Example Imagery object loads
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
|
||||||
|
|
||||||
|
//Get background-image url from background-image css prop
|
||||||
|
const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
|
||||||
|
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
||||||
|
});
|
||||||
|
backgroundImageUrl = backgroundImageUrl.slice(1, -1); //forgive me, padre
|
||||||
|
console.log('backgroundImageurl ' + backgroundImageUrl);
|
||||||
|
|
||||||
|
//Get ResourceTiming of background-image jpg
|
||||||
|
const resourceTimingJson = await page.evaluate((bgImageUrl) =>
|
||||||
|
JSON.stringify(window.performance.getEntriesByName(bgImageUrl).pop()),
|
||||||
|
backgroundImageUrl
|
||||||
|
);
|
||||||
|
console.log('resourceTimingJson ' + resourceTimingJson);
|
||||||
|
|
||||||
|
//Open Large view
|
||||||
|
await page.locator('button:has-text("Large View")').click(); //This action includes the performance.mark named 'viewLarge.start'
|
||||||
|
await page.evaluate(() => window.performance.mark("viewLarge.start.test")); //This is a mark only to compare evaluate timing
|
||||||
|
|
||||||
|
//Time to Imagery Rendered in Large Frame
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'});
|
||||||
|
await page.evaluate(() => window.performance.mark("background-image-frame"));
|
||||||
|
|
||||||
|
//Time to Example Imagery object loads
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
|
||||||
|
await page.evaluate(() => window.performance.mark("background-image-visible"));
|
||||||
|
|
||||||
|
// Get Current number of images in thumbstrip
|
||||||
|
await page.waitForSelector('.c-imagery__thumb');
|
||||||
|
const thumbCount = await page.locator('.c-imagery__thumb').count();
|
||||||
|
console.log('number of thumbs rendered ' + thumbCount);
|
||||||
|
await page.locator('.c-imagery__thumb').last().click();
|
||||||
|
|
||||||
|
//Get ResourceTiming of all jpg resources
|
||||||
|
const resourceTimingJson2 = await page.evaluate(() =>
|
||||||
|
JSON.stringify(window.performance.getEntriesByType('resource'))
|
||||||
|
);
|
||||||
|
const resourceTiming = JSON.parse(resourceTimingJson2);
|
||||||
|
const jpgResourceTiming = resourceTiming.find((element) =>
|
||||||
|
element.name.includes('.jpg')
|
||||||
|
);
|
||||||
|
console.log('jpgResourceTiming ' + JSON.stringify(jpgResourceTiming));
|
||||||
|
|
||||||
|
// Click Close Icon
|
||||||
|
await page.locator('.c-click-icon').click();
|
||||||
|
await page.evaluate(() => window.performance.mark("view-large-close-button"));
|
||||||
|
|
||||||
|
//await client.send('HeapProfiler.enable');
|
||||||
|
await client.send('HeapProfiler.collectGarbage');
|
||||||
|
|
||||||
|
let performanceMetrics = await client.send('Performance.getMetrics');
|
||||||
|
console.log(performanceMetrics.metrics);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
119
e2e/tests/performance/memleak-imagery.perf.spec.js
Normal file
119
e2e/tests/performance/memleak-imagery.perf.spec.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 an initial example for memory leak testing using performance. This configuration and execution must
|
||||||
|
be kept separate from the traditional performance measurements to avoid any "observer" effects associated with tracing
|
||||||
|
or profiling playwright and/or the browser.
|
||||||
|
|
||||||
|
Based on a pattern identified in https://github.com/trentmwillis/devtools-protocol-demos/blob/master/testing-demos/memory-leak-by-heap.js
|
||||||
|
and https://github.com/paulirish/automated-chrome-profiling/issues/3
|
||||||
|
|
||||||
|
Best path forward: https://github.com/cowchimp/headless-devtools/blob/master/src/Memory/example.js
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
|
||||||
|
|
||||||
|
// eslint-disable-next-line playwright/no-skipped-test
|
||||||
|
test.describe.skip('Memory Performance tests', () => {
|
||||||
|
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click a:has-text("My Items")
|
||||||
|
await page.locator('a:has-text("My Items")').click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click text=Import from JSON
|
||||||
|
await page.locator('text=Import from JSON').click();
|
||||||
|
|
||||||
|
// Upload Performance Display Layout.json
|
||||||
|
await page.setInputFiles('#fileElem', filePath);
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await page.locator('text=OK').click();
|
||||||
|
|
||||||
|
await expect(page.locator('a:has-text("Performance Display Layout Display Layout")')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => {
|
||||||
|
|
||||||
|
await page.goto('/', {waitUntil: 'networkidle'});
|
||||||
|
|
||||||
|
// To to Search Available after Launch
|
||||||
|
await page.locator('input[type="search"]').click();
|
||||||
|
// Fill Search input
|
||||||
|
await page.locator('input[type="search"]').fill('Performance Display Layout');
|
||||||
|
//Search Result Appears and is clicked
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('a:has-text("Performance Display Layout")').first().click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
//Time to Example Imagery Frame loads within Display Layout
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'});
|
||||||
|
//Time to Example Imagery object loads
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
|
||||||
|
|
||||||
|
const client = await page.context().newCDPSession(page);
|
||||||
|
await client.send('HeapProfiler.enable');
|
||||||
|
await client.send('HeapProfiler.startSampling');
|
||||||
|
// await client.send('HeapProfiler.collectGarbage');
|
||||||
|
await client.send('Performance.enable');
|
||||||
|
|
||||||
|
let performanceMetricsBefore = await client.send('Performance.getMetrics');
|
||||||
|
console.log(performanceMetricsBefore.metrics);
|
||||||
|
|
||||||
|
//await client.send('Performance.disable');
|
||||||
|
|
||||||
|
//Open Large view
|
||||||
|
await page.locator('button:has-text("Large View")').click();
|
||||||
|
await client.send('HeapProfiler.takeHeapSnapshot');
|
||||||
|
|
||||||
|
//Time to Imagery Rendered in Large Frame
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'});
|
||||||
|
|
||||||
|
//Time to Example Imagery object loads
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
|
||||||
|
|
||||||
|
// Click Close Icon
|
||||||
|
await page.locator('.c-click-icon').click();
|
||||||
|
|
||||||
|
//Time to Example Imagery Frame loads within Display Layout
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible'});
|
||||||
|
//Time to Example Imagery object loads
|
||||||
|
await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible'});
|
||||||
|
|
||||||
|
await client.send('HeapProfiler.collectGarbage');
|
||||||
|
//await client.send('Performance.enable');
|
||||||
|
|
||||||
|
let performanceMetricsAfter = await client.send('Performance.getMetrics');
|
||||||
|
console.log(performanceMetricsAfter.metrics);
|
||||||
|
|
||||||
|
//await client.send('Performance.disable');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
158
e2e/tests/performance/notebook.perf.spec.js
Normal file
158
e2e/tests/performance/notebook.perf.spec.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 performance tests to ensure that testability of performance
|
||||||
|
is not broken upstream on Open MCT. Any assumptions made downstream will be tested here.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- Update resolution of performance config
|
||||||
|
- Add Performance Observer on init to push all performance marks
|
||||||
|
- Move client CDP connection to before or to a fixture
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json';
|
||||||
|
|
||||||
|
test.describe('Performance tests', () => {
|
||||||
|
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Click a:has-text("My Items")
|
||||||
|
await page.locator('a:has-text("My Items")').click({
|
||||||
|
button: 'right'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click text=Import from JSON
|
||||||
|
await page.locator('text=Import from JSON').click();
|
||||||
|
|
||||||
|
// Upload Performance Display Layout.json
|
||||||
|
await page.setInputFiles('#fileElem', notebookFilePath);
|
||||||
|
|
||||||
|
// TODO Fix this
|
||||||
|
await page.locator('text=OK >> nth=1').click();
|
||||||
|
|
||||||
|
await expect(page.locator('a:has-text("Performance Notebook")')).toBeVisible();
|
||||||
|
|
||||||
|
//Create a Chrome Performance Timeline trace to store as a test artifact
|
||||||
|
console.log("\n==== Devtools: startTracing ====\n");
|
||||||
|
await browser.startTracing(page, {
|
||||||
|
path: `${testInfo.outputPath()}-trace.json`,
|
||||||
|
screenshots: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test.afterEach(async ({ page, browser}) => {
|
||||||
|
console.log("\n==== Devtools: stopTracing ====\n");
|
||||||
|
await browser.stopTracing();
|
||||||
|
|
||||||
|
/* Measurement Section
|
||||||
|
/ The following section includes a block of performance measurements.
|
||||||
|
*/
|
||||||
|
const startTime = await page.evaluate(() => window.performance.timing.navigationStart);
|
||||||
|
console.log('window.performance.timing.navigationStart', startTime);
|
||||||
|
|
||||||
|
//Get All Performance Marks
|
||||||
|
const getAllMarksJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("mark")));
|
||||||
|
const getAllMarks = JSON.parse(getAllMarksJson);
|
||||||
|
console.log('window.performance.getEntriesByType("mark")', getAllMarks);
|
||||||
|
|
||||||
|
//Get All Performance Measures
|
||||||
|
const getAllMeasuresJson = await page.evaluate(() => JSON.stringify(window.performance.getEntriesByType("measure")));
|
||||||
|
const getAllMeasures = JSON.parse(getAllMeasuresJson);
|
||||||
|
console.log('window.performance.getEntriesByType("measure")', getAllMeasures);
|
||||||
|
|
||||||
|
});
|
||||||
|
/* The following test will navigate to a previously created Performance Display Layout and measure the
|
||||||
|
/ following metrics:
|
||||||
|
/ - ElementResourceTiming
|
||||||
|
/ - Interaction Timing
|
||||||
|
*/
|
||||||
|
test('Notebook Search, Add Entry, Update Entry are performant', async ({ page, browser }) => {
|
||||||
|
const client = await page.context().newCDPSession(page);
|
||||||
|
// Tell the DevTools session to record performance metrics
|
||||||
|
// https://chromedevtools.github.io/devtools-protocol/tot/Performance/#method-getMetrics
|
||||||
|
await client.send('Performance.enable');
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
// To to Search Available after Launch
|
||||||
|
await page.locator('input[type="search"]').click();
|
||||||
|
await page.evaluate(() => window.performance.mark("search-available"));
|
||||||
|
// Fill Search input
|
||||||
|
await page.locator('input[type="search"]').fill('Performance Notebook');
|
||||||
|
await page.evaluate(() => window.performance.mark("search-entered"));
|
||||||
|
//Search Result Appears and is clicked
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('a:has-text("Performance Notebook")').first().click(),
|
||||||
|
page.evaluate(() => window.performance.mark("click-search-result"))
|
||||||
|
]);
|
||||||
|
|
||||||
|
await page.waitForSelector('.c-tree__item c-tree-and-search__loading loading', {state: 'hidden'});
|
||||||
|
await page.evaluate(() => window.performance.mark("search-spinner-gone"));
|
||||||
|
|
||||||
|
await page.waitForSelector('.l-browse-bar__object-name', { state: 'visible'});
|
||||||
|
await page.evaluate(() => window.performance.mark("object-title-appears"));
|
||||||
|
|
||||||
|
await page.waitForSelector('.c-notebook__entry >> nth=0', { state: 'visible'});
|
||||||
|
await page.evaluate(() => window.performance.mark("notebook-entry-appears"));
|
||||||
|
|
||||||
|
// Click Add new Notebook Entry
|
||||||
|
await page.locator('.c-notebook__drag-area').click();
|
||||||
|
await page.evaluate(() => window.performance.mark("new-notebook-entry-created"));
|
||||||
|
|
||||||
|
// Enter Notebook Entry text
|
||||||
|
await page.locator('div.c-ne__text').last().fill('New Entry');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.evaluate(() => window.performance.mark("new-notebook-entry-filled"));
|
||||||
|
|
||||||
|
//Individual Notebook Entry Search
|
||||||
|
await page.evaluate(() => window.performance.mark("notebook-search-start"));
|
||||||
|
await page.locator('.c-notebook__search >> input').fill('Existing Entry');
|
||||||
|
await page.evaluate(() => window.performance.mark("notebook-search-filled"));
|
||||||
|
await page.waitForSelector('text=Search Results (3)', { state: 'visible'});
|
||||||
|
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
|
||||||
|
await page.waitForSelector('.c-notebook__entry >> nth=2', { state: 'visible'});
|
||||||
|
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
|
||||||
|
|
||||||
|
//Clear Search
|
||||||
|
await page.locator('.c-search.c-notebook__search .c-search__clear-input').click();
|
||||||
|
await page.evaluate(() => window.performance.mark("notebook-search-processed"));
|
||||||
|
|
||||||
|
// Hover on Last
|
||||||
|
await page.evaluate(() => window.performance.mark("new-notebook-entry-delete"));
|
||||||
|
await page.locator('div.c-ne__time-and-content').last().hover();
|
||||||
|
await page.locator('button[title="Delete this entry"]').last().click();
|
||||||
|
await page.locator('button:has-text("Ok")').click();
|
||||||
|
await page.waitForSelector('.c-notebook__entry >> nth=3', { state: 'detached'});
|
||||||
|
await page.evaluate(() => window.performance.mark("new-notebook-entry-deleted"));
|
||||||
|
|
||||||
|
//await client.send('HeapProfiler.enable');
|
||||||
|
await client.send('HeapProfiler.collectGarbage');
|
||||||
|
|
||||||
|
let performanceMetrics = await client.send('Performance.getMetrics');
|
||||||
|
console.log(performanceMetrics.metrics);
|
||||||
|
});
|
||||||
|
});
|
@ -25,6 +25,8 @@ This test suite is dedicated to tests which verify the basic operations surround
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { test } = require('../../../fixtures.js');
|
const { test } = require('../../../fixtures.js');
|
||||||
|
// FIXME: Remove this eslint exception once tests are implemented
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { expect } = require('@playwright/test');
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('ExportAsJSON', () => {
|
test.describe('ExportAsJSON', () => {
|
||||||
|
@ -25,6 +25,8 @@ This test suite is dedicated to tests which verify the basic operations surround
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { test } = require('../../../fixtures.js');
|
const { test } = require('../../../fixtures.js');
|
||||||
|
// FIXME: Remove this eslint exception once tests are implemented
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { expect } = require('@playwright/test');
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('ExportAsJSON', () => {
|
test.describe('ExportAsJSON', () => {
|
||||||
|
@ -42,6 +42,9 @@ test('Create new Condition Set object and store @localStorage', async ({ page, c
|
|||||||
// Click text=Condition Set
|
// Click text=Condition Set
|
||||||
await page.click('text=Condition Set');
|
await page.click('text=Condition Set');
|
||||||
|
|
||||||
|
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
||||||
|
await page.click('form[name="mctForm"] a:has-text("My Items")');
|
||||||
|
|
||||||
// Click text=OK
|
// Click text=OK
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
|
@ -59,7 +59,7 @@ test.describe('Example Imagery', () => {
|
|||||||
|
|
||||||
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
||||||
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
|
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
|
||||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
const deltaYStep = 100; //equivalent to 1x zoom
|
const deltaYStep = 100; //equivalent to 1x zoom
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover();
|
||||||
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
@ -87,7 +87,7 @@ test.describe('Example Imagery', () => {
|
|||||||
const deltaYStep = 100; //equivalent to 1x zoom
|
const deltaYStep = 100; //equivalent to 1x zoom
|
||||||
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
const panHotkey = process.platform === 'linux' ? ['Control', 'Alt'] : ['Alt'];
|
||||||
|
|
||||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover();
|
||||||
|
|
||||||
// zoom in
|
// zoom in
|
||||||
@ -150,10 +150,10 @@ test.describe('Example Imagery', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can use + - buttons to zoom on the image', async ({ page }) => {
|
test('Can use + - buttons to zoom on the image', async ({ page }) => {
|
||||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover();
|
||||||
const zoomInBtn = await page.locator('.t-btn-zoom-in');
|
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
||||||
const zoomOutBtn = await page.locator('.t-btn-zoom-out');
|
const zoomOutBtn = page.locator('.t-btn-zoom-out');
|
||||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||||
|
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
@ -174,12 +174,12 @@ test.describe('Example Imagery', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can use the reset button to reset the image', async ({ page }) => {
|
test('Can use the reset button to reset the image', async ({ page }) => {
|
||||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await bgImageLocator.hover();
|
await bgImageLocator.hover();
|
||||||
|
|
||||||
const zoomInBtn = await page.locator('.t-btn-zoom-in');
|
const zoomInBtn = page.locator('.t-btn-zoom-in');
|
||||||
const zoomResetBtn = await page.locator('.t-btn-zoom-reset');
|
const zoomResetBtn = page.locator('.t-btn-zoom-reset');
|
||||||
const initialBoundingBox = await bgImageLocator.boundingBox();
|
const initialBoundingBox = await bgImageLocator.boundingBox();
|
||||||
|
|
||||||
await zoomInBtn.click();
|
await zoomInBtn.click();
|
||||||
@ -235,113 +235,224 @@ test.describe('Example Imagery', () => {
|
|||||||
// ('If the imagery view is not in pause mode, it should be updated when new images come in');
|
// ('If the imagery view is not in pause mode, it should be updated when new images come in');
|
||||||
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
const backgroundImageSelector = '.c-imagery__main-image__background-image';
|
||||||
test('Example Imagery in Display layout', async ({ page }) => {
|
test('Example Imagery in Display layout', async ({ page }) => {
|
||||||
// Go to baseURL
|
test.info().annotations.push({
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/5265'
|
||||||
|
});
|
||||||
|
|
||||||
// Click the Create button
|
// Go to baseURL
|
||||||
await page.click('button:has-text("Create")');
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Click text=Example Imagery
|
// Click the Create button
|
||||||
await page.click('text=Example Imagery');
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
// Clear and set Image load delay (milliseconds)
|
// Click text=Example Imagery
|
||||||
await page.click('input[type="number"]', {clickCount: 3})
|
await page.click('text=Example Imagery');
|
||||||
await page.type('input[type="number"]', "20")
|
|
||||||
|
|
||||||
// Click text=OK
|
// Clear and set Image load delay to minimum value
|
||||||
await Promise.all([
|
// FIXME: Update the value to 5000 ms when this bug is fixed.
|
||||||
page.waitForNavigation({waitUntil: 'networkidle'}),
|
// See: https://github.com/nasa/openmct/issues/5265
|
||||||
page.click('text=OK'),
|
await page.locator('input[type="number"]').fill('');
|
||||||
//Wait for Save Banner to appear
|
await page.locator('input[type="number"]').fill('0');
|
||||||
page.waitForSelector('.c-message-banner__message')
|
|
||||||
]);
|
|
||||||
// Wait until Save Banner is gone
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
|
||||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
|
||||||
const bgImageLocator = await page.locator(backgroundImageSelector);
|
|
||||||
await bgImageLocator.hover();
|
|
||||||
|
|
||||||
// Click previous image button
|
// Click text=OK
|
||||||
const previousImageButton = await page.locator('.c-nav--prev');
|
await Promise.all([
|
||||||
await previousImageButton.click();
|
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||||
|
page.click('text=OK'),
|
||||||
|
//Wait for Save Banner to appear
|
||||||
|
page.waitForSelector('.c-message-banner__message')
|
||||||
|
]);
|
||||||
|
|
||||||
// Verify previous image
|
// Wait until Save Banner is gone
|
||||||
const selectedImage = page.locator('.selected');
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
await expect(selectedImage).toBeVisible();
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Example Imagery');
|
||||||
|
const bgImageLocator = page.locator(backgroundImageSelector);
|
||||||
|
await bgImageLocator.hover();
|
||||||
|
|
||||||
// Zoom in
|
// Click previous image button
|
||||||
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
const previousImageButton = page.locator('.c-nav--prev');
|
||||||
await bgImageLocator.hover();
|
await previousImageButton.click();
|
||||||
const deltaYStep = 100; // equivalent to 1x zoom
|
|
||||||
await page.mouse.wheel(0, deltaYStep * 2);
|
|
||||||
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
|
||||||
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
|
|
||||||
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
|
|
||||||
|
|
||||||
// Wait for zoom animation to finish
|
// Verify previous image
|
||||||
await bgImageLocator.hover();
|
const selectedImage = page.locator('.selected');
|
||||||
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
await expect(selectedImage).toBeVisible();
|
||||||
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
|
||||||
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
|
||||||
|
|
||||||
// Center the mouse pointer
|
// Zoom in
|
||||||
await page.mouse.move(imageCenterX, imageCenterY);
|
const originalImageDimensions = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
|
await bgImageLocator.hover();
|
||||||
|
const deltaYStep = 100; // equivalent to 1x zoom
|
||||||
|
await page.mouse.wheel(0, deltaYStep * 2);
|
||||||
|
const zoomedBoundingBox = await bgImageLocator.boundingBox();
|
||||||
|
const imageCenterX = zoomedBoundingBox.x + zoomedBoundingBox.width / 2;
|
||||||
|
const imageCenterY = zoomedBoundingBox.y + zoomedBoundingBox.height / 2;
|
||||||
|
|
||||||
// Pan Imagery Hints
|
// Wait for zoom animation to finish
|
||||||
console.log('process.platform is '+process.platform);
|
await bgImageLocator.hover();
|
||||||
const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
|
const imageMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
|
expect(imageMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||||
expect(expectedAltText).toEqual(imageryHintsText);
|
expect(imageMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
||||||
|
|
||||||
// Click next image button
|
// Center the mouse pointer
|
||||||
const nextImageButton = await page.locator('.c-nav--next');
|
await page.mouse.move(imageCenterX, imageCenterY);
|
||||||
await nextImageButton.click();
|
|
||||||
|
|
||||||
// Click fixed timespan button
|
// Pan Imagery Hints
|
||||||
await page.locator('.c-button__label >> text=Fixed Timespan').click();
|
const expectedAltText = process.platform === 'linux' ? 'Ctrl+Alt drag to pan' : 'Alt drag to pan';
|
||||||
|
const imageryHintsText = await page.locator('.c-imagery__hints').innerText();
|
||||||
|
expect(expectedAltText).toEqual(imageryHintsText);
|
||||||
|
|
||||||
// Click local clock
|
// Click next image button
|
||||||
await page.locator('.icon-clock >> text=Local Clock').click();
|
const nextImageButton = page.locator('.c-nav--next');
|
||||||
|
await nextImageButton.click();
|
||||||
|
|
||||||
// Zoom in on next image
|
// Click time conductor mode button
|
||||||
await bgImageLocator.hover();
|
await page.locator('.c-mode-button').click();
|
||||||
await page.mouse.wheel(0, deltaYStep * 2);
|
|
||||||
|
|
||||||
// Wait for zoom animation to finish
|
// Select local clock mode
|
||||||
await bgImageLocator.hover();
|
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
|
||||||
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
|
||||||
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
|
||||||
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
|
||||||
|
|
||||||
// Click previous image button
|
// Zoom in on next image
|
||||||
await previousImageButton.click();
|
await bgImageLocator.hover();
|
||||||
|
await page.mouse.wheel(0, deltaYStep * 2);
|
||||||
|
|
||||||
// Verify previous image
|
// Wait for zoom animation to finish
|
||||||
await expect(selectedImage).toBeVisible();
|
await bgImageLocator.hover();
|
||||||
|
const imageNextMouseZoomedIn = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
|
expect(imageNextMouseZoomedIn.height).toBeGreaterThan(originalImageDimensions.height);
|
||||||
|
expect(imageNextMouseZoomedIn.width).toBeGreaterThan(originalImageDimensions.width);
|
||||||
|
|
||||||
// Wait 20ms to verify no new image has come in
|
// Click previous image button
|
||||||
await page.waitForTimeout(21);
|
await previousImageButton.click();
|
||||||
|
|
||||||
//Get background-image url from background-image css prop
|
// Verify previous image
|
||||||
const backgroundImage = await page.locator('.c-imagery__main-image__background-image');
|
await expect(selectedImage).toBeVisible();
|
||||||
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
|
|
||||||
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
|
||||||
});
|
|
||||||
let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
|
|
||||||
console.log('backgroundImageUrl1 ' + backgroundImageUrl1)
|
|
||||||
|
|
||||||
// sleep 21ms
|
const imageCount = await page.locator('.c-imagery__thumb').count();
|
||||||
await page.waitForTimeout(21);
|
await expect.poll(async () => {
|
||||||
|
const newImageCount = await page.locator('.c-imagery__thumb').count();
|
||||||
|
|
||||||
// Verify next image has updated
|
return newImageCount;
|
||||||
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
|
}, {
|
||||||
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
message: "verify that new images still stream in",
|
||||||
});
|
timeout: 6 * 1000
|
||||||
let backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
|
}).toBeGreaterThan(imageCount);
|
||||||
console.log('backgroundImageUrl2 ' + backgroundImageUrl2)
|
|
||||||
|
|
||||||
// Expect backgroundImageUrl2 to be greater then backgroundImageUrl1
|
// Verify selected image is still displayed
|
||||||
expect(backgroundImageUrl2 >= backgroundImageUrl1);
|
await expect(selectedImage).toBeVisible();
|
||||||
|
|
||||||
|
// Unpause imagery
|
||||||
|
await page.locator('.pause-play').click();
|
||||||
|
|
||||||
|
//Get background-image url from background-image css prop
|
||||||
|
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
|
||||||
|
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
||||||
|
});
|
||||||
|
let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
|
||||||
|
console.log('backgroundImageUrl1 ' + backgroundImageUrl1);
|
||||||
|
|
||||||
|
let backgroundImageUrl2;
|
||||||
|
await expect.poll(async () => {
|
||||||
|
// Verify next image has updated
|
||||||
|
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
|
||||||
|
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
||||||
|
});
|
||||||
|
backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
|
||||||
|
|
||||||
|
return backgroundImageUrl2;
|
||||||
|
}, {
|
||||||
|
message: "verify next image has updated",
|
||||||
|
timeout: 6 * 1000
|
||||||
|
}).not.toBe(backgroundImageUrl1);
|
||||||
|
console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Example imagery thumbnails resize in display layouts', () => {
|
||||||
|
|
||||||
|
test('Resizing the layout changes thumbnail visibility and size', async ({ page }) => {
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
const thumbsWrapperLocator = page.locator('.c-imagery__thumbs-wrapper');
|
||||||
|
// Click button:has-text("Create")
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
|
||||||
|
// Click li:has-text("Display Layout")
|
||||||
|
await page.locator('li:has-text("Display Layout")').click();
|
||||||
|
const displayLayoutTitleField = page.locator('text=Properties Title Notes Horizontal grid (px) Vertical grid (px) Horizontal size ( >> input[type="text"]');
|
||||||
|
await displayLayoutTitleField.click();
|
||||||
|
|
||||||
|
await displayLayoutTitleField.fill('Thumbnail Display Layout');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=OK').click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
|
||||||
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
|
|
||||||
|
// Click text=Save and Finish Editing
|
||||||
|
await page.locator('text=Save and Finish Editing').click();
|
||||||
|
|
||||||
|
// Click button:has-text("Create")
|
||||||
|
await page.locator('button:has-text("Create")').click();
|
||||||
|
|
||||||
|
// Click li:has-text("Example Imagery")
|
||||||
|
await page.locator('li:has-text("Example Imagery")').click();
|
||||||
|
|
||||||
|
const imageryTitleField = page.locator('text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]');
|
||||||
|
// Click text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]
|
||||||
|
await imageryTitleField.click();
|
||||||
|
|
||||||
|
// Fill text=Properties Title Notes Images url list (comma separated) Image load delay (milli >> input[type="text"]
|
||||||
|
await imageryTitleField.fill('Thumbnail Example Imagery');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=OK').click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Click text=Thumbnail Example Imagery Imagery Layout Snapshot >> button >> nth=0
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator('text=Thumbnail Example Imagery Imagery Layout Snapshot >> button').first().click()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Edit mode
|
||||||
|
await page.locator('text=Thumbnail Display Layout Snapshot >> button').nth(3).click();
|
||||||
|
|
||||||
|
// Click on example imagery to expose toolbar
|
||||||
|
await page.locator('text=Thumbnail Example Imagery Snapshot Large View').click();
|
||||||
|
|
||||||
|
// expect thumbnails not be visible when first added
|
||||||
|
expect.soft(thumbsWrapperLocator.isHidden()).toBeTruthy();
|
||||||
|
|
||||||
|
// Resize the example imagery vertically to change the thumbnail visibility
|
||||||
|
/*
|
||||||
|
The following arbitrary values are added to observe the separate visual
|
||||||
|
conditions of the thumbnails (hidden, small thumbnails, regular thumbnails).
|
||||||
|
Specifically, height is set to 50px for small thumbs and 100px for regular
|
||||||
|
*/
|
||||||
|
// Click #mct-input-id-103
|
||||||
|
await page.locator('#mct-input-id-103').click();
|
||||||
|
|
||||||
|
// Fill #mct-input-id-103
|
||||||
|
await page.locator('#mct-input-id-103').fill('50');
|
||||||
|
|
||||||
|
expect(thumbsWrapperLocator.isVisible()).toBeTruthy();
|
||||||
|
await expect(thumbsWrapperLocator).toHaveClass(/is-small-thumbs/);
|
||||||
|
|
||||||
|
// Resize the example imagery vertically to change the thumbnail visibility
|
||||||
|
// Click #mct-input-id-103
|
||||||
|
await page.locator('#mct-input-id-103').click();
|
||||||
|
|
||||||
|
// Fill #mct-input-id-103
|
||||||
|
await page.locator('#mct-input-id-103').fill('100');
|
||||||
|
|
||||||
|
expect(thumbsWrapperLocator.isVisible()).toBeTruthy();
|
||||||
|
await expect(thumbsWrapperLocator).not.toHaveClass(/is-small-thumbs/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Example Imagery in Flexible layout', () => {
|
test.describe('Example Imagery in Flexible layout', () => {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
@ -46,8 +46,11 @@ test.describe('Log plot tests', () => {
|
|||||||
await testLogTicks(page);
|
await testLogTicks(page);
|
||||||
//await testLogPlotPixels(page);
|
//await testLogPlotPixels(page);
|
||||||
|
|
||||||
// refresh page and wait for charts and ticks to load
|
// FIXME: Get rid of the waitForTimeout() and lint warning exception.
|
||||||
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||||
await page.waitForTimeout(1 * 1000);
|
await page.waitForTimeout(1 * 1000);
|
||||||
|
|
||||||
|
// refresh page and wait for charts and ticks to load
|
||||||
await page.reload({ waitUntil: 'networkidle'});
|
await page.reload({ waitUntil: 'networkidle'});
|
||||||
await page.waitForSelector('.gl-plot-chart-area');
|
await page.waitForSelector('.gl-plot-chart-area');
|
||||||
await page.waitForSelector('.gl-plot-y-tick-label');
|
await page.waitForSelector('.gl-plot-y-tick-label');
|
||||||
@ -57,7 +60,9 @@ test.describe('Log plot tests', () => {
|
|||||||
//await testLogPlotPixels(page);
|
//await testLogPlotPixels(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('Verify that log mode option is reflected in import/export JSON', async ({ page }) => {
|
// Leaving test as 'TODO' for now.
|
||||||
|
// NOTE: Not eligible for community contributions.
|
||||||
|
test.fixme('Verify that log mode option is reflected in import/export JSON', async ({ page }) => {
|
||||||
await makeOverlayPlot(page);
|
await makeOverlayPlot(page);
|
||||||
await enableEditMode(page);
|
await enableEditMode(page);
|
||||||
await enableLogMode(page);
|
await enableLogMode(page);
|
||||||
@ -242,6 +247,8 @@ async function saveOverlayPlot(page) {
|
|||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
|
// FIXME: Remove this eslint exception once implemented
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
async function testLogPlotPixels(page) {
|
async function testLogPlotPixels(page) {
|
||||||
const pixelsMatch = await page.evaluate(async () => {
|
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.
|
// 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.
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
const { test } = require('../../../fixtures.js');
|
const { test } = require('../../../fixtures.js');
|
||||||
const { expect } = require('@playwright/test');
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('Time counductor operations', () => {
|
test.describe('Time conductor operations', () => {
|
||||||
test('validate start time does not exceeds end time', async ({ page }) => {
|
test('validate start time does not exceeds end time', async ({ page }) => {
|
||||||
//Go to baseURL
|
// Go to baseURL
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
@ -73,40 +73,163 @@ test.describe('Time counductor operations', () => {
|
|||||||
// Try to change the realtime offsets when in realtime (local clock) mode.
|
// Try to change the realtime offsets when in realtime (local clock) mode.
|
||||||
test.describe('Time conductor input fields real-time mode', () => {
|
test.describe('Time conductor input fields real-time mode', () => {
|
||||||
test('validate input fields in real-time mode', async ({ page }) => {
|
test('validate input fields in real-time mode', async ({ page }) => {
|
||||||
//Go to baseURL
|
const startOffset = {
|
||||||
|
secs: '23'
|
||||||
|
};
|
||||||
|
|
||||||
|
const endOffset = {
|
||||||
|
secs: '31'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Go to baseURL
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Set realtime "local clock" mode offsets
|
// Switch to real-time mode
|
||||||
const timeInputs = page.locator('input.c-input--datetime');
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
// Click fixed timespan button
|
// Set start time offset
|
||||||
await page.locator('.c-button__label >> text=Fixed Timespan').click();
|
await setStartOffset(page, startOffset);
|
||||||
|
|
||||||
// Click local clock
|
|
||||||
await page.locator('.icon-clock >> text=Local Clock').click();
|
|
||||||
|
|
||||||
// Click time offset button
|
|
||||||
await page.locator('.c-conductor__delta-button >> text=00:30:00').click();
|
|
||||||
|
|
||||||
// Input start time offset
|
|
||||||
await page.fill('.pr-time-controls__secs', '23');
|
|
||||||
|
|
||||||
// Click the check button
|
|
||||||
await page.locator('.icon-check').click();
|
|
||||||
|
|
||||||
// Verify time was updated on time offset button
|
// Verify time was updated on time offset button
|
||||||
await expect(page.locator('.c-conductor__delta-button').first()).toContainText('00:30:23');
|
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23');
|
||||||
|
|
||||||
// Click time offset set preceding now button
|
// Set end time offset
|
||||||
await page.locator('.c-conductor__delta-button >> text=00:00:30').click();
|
await setEndOffset(page, endOffset);
|
||||||
|
|
||||||
// Input preceding time offset
|
|
||||||
await page.fill('.pr-time-controls__secs', '31');
|
|
||||||
|
|
||||||
// Click the check buttons
|
|
||||||
await page.locator('.icon-check').click();
|
|
||||||
|
|
||||||
// Verify time was updated on preceding time offset button
|
// Verify time was updated on preceding time offset button
|
||||||
await expect(page.locator('.c-conductor__delta-button').nth(1)).toContainText('00:00:31');
|
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that offsets and url params are preserved when switching
|
||||||
|
* between fixed timespan and real-time mode.
|
||||||
|
*/
|
||||||
|
test('preserve offsets and url params when switching between fixed and real-time mode', async ({ page }) => {
|
||||||
|
const startOffset = {
|
||||||
|
mins: '30',
|
||||||
|
secs: '23'
|
||||||
|
};
|
||||||
|
|
||||||
|
const endOffset = {
|
||||||
|
secs: '01'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert offsets to milliseconds
|
||||||
|
const startDelta = (30 * 60 * 1000) + (23 * 1000);
|
||||||
|
const endDelta = (1 * 1000);
|
||||||
|
|
||||||
|
// Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
// Switch to real-time mode
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
// Set start time offset
|
||||||
|
await setStartOffset(page, startOffset);
|
||||||
|
|
||||||
|
// Set end time offset
|
||||||
|
await setEndOffset(page, endOffset);
|
||||||
|
|
||||||
|
// Switch to fixed timespan mode
|
||||||
|
await setFixedTimeMode(page);
|
||||||
|
|
||||||
|
// Switch back to real-time mode
|
||||||
|
await setRealTimeMode(page);
|
||||||
|
|
||||||
|
// Verify updated start time offset persists after mode switch
|
||||||
|
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText('00:30:23');
|
||||||
|
|
||||||
|
// Verify updated end time offset persists after mode switch
|
||||||
|
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
|
||||||
|
|
||||||
|
// Verify url parameters persist after mode switch
|
||||||
|
await page.waitForNavigation();
|
||||||
|
expect(page.url()).toContain(`startDelta=${startDelta}`);
|
||||||
|
expect(page.url()).toContain(`endDelta=${endDelta}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} OffsetValues
|
||||||
|
* @property {string | undefined} hours
|
||||||
|
* @property {string | undefined} mins
|
||||||
|
* @property {string | undefined} secs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the values (hours, mins, secs) for the start time offset when in realtime mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {OffsetValues} offset
|
||||||
|
*/
|
||||||
|
async function setStartOffset(page, offset) {
|
||||||
|
const startOffsetButton = page.locator('data-testid=conductor-start-offset-button');
|
||||||
|
await setTimeConductorOffset(page, offset, startOffsetButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the values (hours, mins, secs) for the end time offset when in realtime mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {OffsetValues} offset
|
||||||
|
*/
|
||||||
|
async function setEndOffset(page, offset) {
|
||||||
|
const endOffsetButton = page.locator('data-testid=conductor-end-offset-button');
|
||||||
|
await setTimeConductorOffset(page, offset, endOffsetButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time conductor to fixed timespan mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function setFixedTimeMode(page) {
|
||||||
|
await setTimeConductorMode(page, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time conductor to realtime mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
*/
|
||||||
|
async function setRealTimeMode(page) {
|
||||||
|
await setTimeConductorMode(page, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the values (hours, mins, secs) for the TimeConductor offsets when in realtime mode
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {OffsetValues} offset
|
||||||
|
* @param {import('@playwright/test').Locator} offsetButton
|
||||||
|
*/
|
||||||
|
async function setTimeConductorOffset(page, {hours, mins, secs}, offsetButton) {
|
||||||
|
await offsetButton.click();
|
||||||
|
|
||||||
|
if (hours) {
|
||||||
|
await page.fill('.pr-time-controls__hrs', hours);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mins) {
|
||||||
|
await page.fill('.pr-time-controls__mins', mins);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secs) {
|
||||||
|
await page.fill('.pr-time-controls__secs', secs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click the check button
|
||||||
|
await page.locator('.icon-check').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the time conductor mode to either fixed timespan or realtime mode.
|
||||||
|
* @param {import('@playwright/test').Page} page
|
||||||
|
* @param {boolean} [isFixedTimespan=true] true for fixed timespan mode, false for realtime mode; default is true
|
||||||
|
*/
|
||||||
|
async function setTimeConductorMode(page, isFixedTimespan = true) {
|
||||||
|
// Click 'mode' button
|
||||||
|
await page.locator('.c-mode-button').click();
|
||||||
|
|
||||||
|
// Switch time conductor mode
|
||||||
|
if (isFixedTimespan) {
|
||||||
|
await page.locator('data-testid=conductor-modeOption-fixed').click();
|
||||||
|
} else {
|
||||||
|
await page.locator('data-testid=conductor-modeOption-realtime').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -47,7 +47,10 @@ test.beforeEach(async ({ context }) => {
|
|||||||
path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js')
|
path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js')
|
||||||
});
|
});
|
||||||
await context.addInitScript(() => {
|
await context.addInitScript(() => {
|
||||||
window.__clock = sinon.useFakeTimers(); //Set browser clock to UNIX Epoch
|
window.__clock = sinon.useFakeTimers({
|
||||||
|
now: 0,
|
||||||
|
shouldAdvanceTime: true
|
||||||
|
}); //Set browser clock to UNIX Epoch
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -56,8 +59,7 @@ test('Visual - Root and About', async ({ page }) => {
|
|||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
// Verify that Create button is actionable
|
// Verify that Create button is actionable
|
||||||
const createButtonLocator = page.locator('button:has-text("Create")');
|
await expect(page.locator('button:has-text("Create")')).toBeEnabled();
|
||||||
await expect(createButtonLocator).toBeEnabled();
|
|
||||||
|
|
||||||
// Take a snapshot of the Dashboard
|
// Take a snapshot of the Dashboard
|
||||||
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
@ -171,3 +173,24 @@ test('Visual - Sine Wave Generator Form', async ({ page }) => {
|
|||||||
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
await percySnapshot(page, 'removed amplitude property value');
|
await percySnapshot(page, 'removed amplitude property value');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Visual - Save Successful Banner', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
//NOTE Something other than example imagery
|
||||||
|
await page.click('text=Timer');
|
||||||
|
|
||||||
|
// Click text=OK
|
||||||
|
await page.click('text=OK');
|
||||||
|
await page.locator('.c-message-banner__message').hover({ trial: true });
|
||||||
|
await percySnapshot(page, 'Banner message shown');
|
||||||
|
|
||||||
|
//Wait until Save Banner is gone
|
||||||
|
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
||||||
|
await percySnapshot(page, 'Banner message gone');
|
||||||
|
|
||||||
|
});
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'EventEmitter';
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import createExampleUser from './exampleUserCreator';
|
import createExampleUser from './exampleUserCreator';
|
||||||
|
|
||||||
export default class ExampleUserProvider extends EventEmitter {
|
export default class ExampleUserProvider extends EventEmitter {
|
||||||
|
@ -196,6 +196,8 @@
|
|||||||
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.install(openmct.plugins.Timelist());
|
||||||
|
openmct.install(openmct.plugins.BarChart());
|
||||||
|
openmct.install(openmct.plugins.ScatterPlot());
|
||||||
openmct.start();
|
openmct.start();
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
26
package.json
26
package.json
@ -5,15 +5,14 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "7.16.3",
|
"@babel/eslint-parser": "7.16.3",
|
||||||
"@braintree/sanitize-url": "6.0.0",
|
"@braintree/sanitize-url": "6.0.0",
|
||||||
"@percy/cli": "1.0.4",
|
"@percy/cli": "1.2.1",
|
||||||
"@percy/playwright": "1.0.3",
|
"@percy/playwright": "1.0.4",
|
||||||
"@playwright/test": "1.21.1",
|
"@playwright/test": "1.21.1",
|
||||||
"@types/eventemitter3": "^1.0.0",
|
"@types/eventemitter3": "^1.0.0",
|
||||||
"@types/jasmine": "^4.0.1",
|
"@types/jasmine": "^4.0.1",
|
||||||
"@types/karma": "^6.3.2",
|
"@types/karma": "^6.3.2",
|
||||||
"@types/lodash": "^4.14.178",
|
"@types/lodash": "^4.14.178",
|
||||||
"@types/mocha": "^9.1.0",
|
"@types/mocha": "^9.1.0",
|
||||||
"allure-playwright": "2.0.0-beta.15",
|
|
||||||
"babel-loader": "8.2.3",
|
"babel-loader": "8.2.3",
|
||||||
"babel-plugin-istanbul": "6.1.1",
|
"babel-plugin-istanbul": "6.1.1",
|
||||||
"comma-separated-values": "3.6.4",
|
"comma-separated-values": "3.6.4",
|
||||||
@ -35,9 +34,9 @@
|
|||||||
"git-rev-sync": "3.0.2",
|
"git-rev-sync": "3.0.2",
|
||||||
"html2canvas": "1.4.1",
|
"html2canvas": "1.4.1",
|
||||||
"imports-loader": "0.8.0",
|
"imports-loader": "0.8.0",
|
||||||
"jasmine-core": "4.0.1",
|
"jasmine-core": "4.1.1",
|
||||||
"jsdoc": "3.5.5",
|
"jsdoc": "3.5.5",
|
||||||
"karma": "6.3.18",
|
"karma": "6.3.20",
|
||||||
"karma-chrome-launcher": "3.1.1",
|
"karma-chrome-launcher": "3.1.1",
|
||||||
"karma-cli": "2.0.0",
|
"karma-cli": "2.0.0",
|
||||||
"karma-coverage": "2.2.0",
|
"karma-coverage": "2.2.0",
|
||||||
@ -48,7 +47,7 @@
|
|||||||
"karma-sourcemap-loader": "0.3.8",
|
"karma-sourcemap-loader": "0.3.8",
|
||||||
"karma-spec-reporter": "0.0.34",
|
"karma-spec-reporter": "0.0.34",
|
||||||
"karma-webpack": "5.0.0",
|
"karma-webpack": "5.0.0",
|
||||||
"lighthouse": "9.5.0",
|
"lighthouse": "9.6.1",
|
||||||
"location-bar": "3.0.1",
|
"location-bar": "3.0.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mini-css-extract-plugin": "2.6.0",
|
"mini-css-extract-plugin": "2.6.0",
|
||||||
@ -64,16 +63,16 @@
|
|||||||
"resolve-url-loader": "5.0.0",
|
"resolve-url-loader": "5.0.0",
|
||||||
"sass": "1.49.9",
|
"sass": "1.49.9",
|
||||||
"sass-loader": "12.6.0",
|
"sass-loader": "12.6.0",
|
||||||
"sinon": "13.0.1",
|
"sinon": "14.0.0",
|
||||||
"style-loader": "^1.0.1",
|
"style-loader": "^1.0.1",
|
||||||
"uuid": "3.3.3",
|
"uuid": "8.3.2",
|
||||||
"vue": "2.6.14",
|
"vue": "2.6.14",
|
||||||
"vue-eslint-parser": "8.3.0",
|
"vue-eslint-parser": "8.3.0",
|
||||||
"vue-loader": "15.9.8",
|
"vue-loader": "15.9.8",
|
||||||
"vue-template-compiler": "2.6.14",
|
"vue-template-compiler": "2.6.14",
|
||||||
"webpack": "5.68.0",
|
"webpack": "5.68.0",
|
||||||
"webpack-cli": "4.9.2",
|
"webpack-cli": "4.9.2",
|
||||||
"webpack-dev-middleware": "5.3.1",
|
"webpack-dev-middleware": "5.3.3",
|
||||||
"webpack-hot-middleware": "2.25.1",
|
"webpack-hot-middleware": "2.25.1",
|
||||||
"webpack-merge": "5.8.0",
|
"webpack-merge": "5.8.0",
|
||||||
"zepto": "1.2.0"
|
"zepto": "1.2.0"
|
||||||
@ -82,8 +81,8 @@
|
|||||||
"clean": "rm -rf ./dist ./node_modules ./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 e2e --ext .js,.vue openmct.js --max-warnings=0",
|
||||||
"lint:fix": "eslint example src --ext .js,.vue openmct.js --fix",
|
"lint:fix": "eslint example src e2e --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:coverage": "webpack --config webpack.coverage.js",
|
||||||
@ -92,11 +91,12 @@
|
|||||||
"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: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:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke default condition timeConductor branding clock exampleImagery",
|
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery notebook persistence performance",
|
||||||
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
||||||
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots",
|
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome --grep @snapshot --update-snapshots",
|
||||||
"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",
|
||||||
"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:perf": "npx playwright test --config=e2e/playwright-performance.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",
|
||||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
||||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2022/gm' ./src/ui/layout/AboutDialog.vue",
|
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2022/gm' ./src/ui/layout/AboutDialog.vue",
|
||||||
|
@ -242,8 +242,6 @@ define([
|
|||||||
|
|
||||||
// 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.ScatterPlot());
|
|
||||||
this.install(this.plugins.BarChart());
|
|
||||||
this.install(this.plugins.TelemetryTable.default());
|
this.install(this.plugins.TelemetryTable.default());
|
||||||
this.install(PreviewPlugin.default());
|
this.install(PreviewPlugin.default());
|
||||||
this.install(LicensesPlugin.default());
|
this.install(LicensesPlugin.default());
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FormRow from "@/api/forms/components/FormRow.vue";
|
import FormRow from "@/api/forms/components/FormRow.vue";
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
import toggleMixin from '../../toggle-check-box-mixin';
|
import toggleMixin from '../../toggle-check-box-mixin';
|
||||||
import ToggleSwitch from '@/ui/components/ToggleSwitch.vue';
|
import ToggleSwitch from '@/ui/components/ToggleSwitch.vue';
|
||||||
|
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
|
:data-testid="action.testId || false"
|
||||||
@click="action.onItemClicked"
|
@click="action.onItemClicked"
|
||||||
>
|
>
|
||||||
{{ action.name }}
|
{{ action.name }}
|
||||||
@ -37,6 +38,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="action.cssClass"
|
:class="action.cssClass"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
|
:data-testid="action.testId || false"
|
||||||
@click="action.onItemClicked"
|
@click="action.onItemClicked"
|
||||||
>
|
>
|
||||||
{{ action.name }}
|
{{ action.name }}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
|
:data-testid="action.testId || false"
|
||||||
@click="action.onItemClicked"
|
@click="action.onItemClicked"
|
||||||
@mouseover="toggleItemDescription(action)"
|
@mouseover="toggleItemDescription(action)"
|
||||||
@mouseleave="toggleItemDescription()"
|
@mouseleave="toggleItemDescription()"
|
||||||
@ -45,6 +46,7 @@
|
|||||||
:key="action.name"
|
:key="action.name"
|
||||||
:class="action.cssClass"
|
:class="action.cssClass"
|
||||||
:title="action.description"
|
:title="action.description"
|
||||||
|
:data-testid="action.testId || false"
|
||||||
@click="action.onItemClicked"
|
@click="action.onItemClicked"
|
||||||
@mouseover="toggleItemDescription(action)"
|
@mouseover="toggleItemDescription(action)"
|
||||||
@mouseleave="toggleItemDescription()"
|
@mouseleave="toggleItemDescription()"
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
class InMemorySearchProvider {
|
class InMemorySearchProvider {
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +33,7 @@ function replaceDotsWithUnderscores(filename) {
|
|||||||
|
|
||||||
import {saveAs} from 'saveAs';
|
import {saveAs} from 'saveAs';
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
class ImageExporter {
|
class ImageExporter {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
@ -51,7 +51,7 @@ class ImageExporter {
|
|||||||
const overlays = this.openmct.overlays;
|
const overlays = this.openmct.overlays;
|
||||||
const dialog = overlays.dialog({
|
const dialog = overlays.dialog({
|
||||||
iconClass: 'info',
|
iconClass: 'info',
|
||||||
message: 'Caputuring an image',
|
message: 'Capturing image, please wait...',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
|
@ -52,7 +52,6 @@ export default (agent, document) => {
|
|||||||
if (agent.isMobile()) {
|
if (agent.isMobile()) {
|
||||||
const mediaQuery = window.matchMedia("(orientation: landscape)");
|
const mediaQuery = window.matchMedia("(orientation: landscape)");
|
||||||
function eventHandler(event) {
|
function eventHandler(event) {
|
||||||
console.log("changed");
|
|
||||||
if (event.matches) {
|
if (event.matches) {
|
||||||
body.classList.remove("portrait");
|
body.classList.remove("portrait");
|
||||||
body.classList.add("landscape");
|
body.classList.add("landscape");
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'EventEmitter';
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
|
import TelemetryCriterion from "./criterion/TelemetryCriterion";
|
||||||
import { evaluateResults } from './utils/evaluator';
|
import { evaluateResults } from './utils/evaluator';
|
||||||
import { getLatestTimestamp } from './utils/time';
|
import { getLatestTimestamp } from './utils/time';
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
import Condition from "./Condition";
|
import Condition from "./Condition";
|
||||||
import { getLatestTimestamp } from './utils/time';
|
import { getLatestTimestamp } from './utils/time';
|
||||||
import uuid from "uuid";
|
import { v4 as uuid } from 'uuid';
|
||||||
import EventEmitter from 'EventEmitter';
|
import EventEmitter from 'EventEmitter';
|
||||||
|
|
||||||
export default class ConditionManager extends EventEmitter {
|
export default class ConditionManager extends EventEmitter {
|
||||||
|
@ -214,7 +214,7 @@
|
|||||||
import Criterion from './Criterion.vue';
|
import Criterion from './Criterion.vue';
|
||||||
import ConditionDescription from "./ConditionDescription.vue";
|
import ConditionDescription from "./ConditionDescription.vue";
|
||||||
import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants";
|
import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants";
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -23,7 +23,7 @@ import ConditionSetViewProvider from './ConditionSetViewProvider.js';
|
|||||||
import ConditionSetCompositionPolicy from "./ConditionSetCompositionPolicy";
|
import ConditionSetCompositionPolicy from "./ConditionSetCompositionPolicy";
|
||||||
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider';
|
import ConditionSetMetadataProvider from './ConditionSetMetadataProvider';
|
||||||
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider';
|
import ConditionSetTelemetryProvider from './ConditionSetTelemetryProvider';
|
||||||
import uuid from "uuid";
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default function ConditionPlugin() {
|
export default function ConditionPlugin() {
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import SubobjectView from './SubobjectView.vue';
|
import SubobjectView from './SubobjectView.vue';
|
||||||
import TelemetryView from './TelemetryView.vue';
|
import TelemetryView from './TelemetryView.vue';
|
||||||
import BoxView from './BoxView.vue';
|
import BoxView from './BoxView.vue';
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class encapsulates the process of duplicating/copying a domain object
|
* This class encapsulates the process of duplicating/copying a domain object
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import JSONExporter from '/src/exporters/JSONExporter.js';
|
import JSONExporter from '/src/exporters/JSONExporter.js';
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import uuid from "uuid";
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default class ExportAsJSONAction {
|
export default class ExportAsJSONAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
class Container {
|
class Container {
|
||||||
constructor(size) {
|
constructor(size) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
class Frame {
|
class Frame {
|
||||||
constructor(domainObjectIdentifier, size) {
|
constructor(domainObjectIdentifier, size) {
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
import PropertiesAction from './PropertiesAction';
|
import PropertiesAction from './PropertiesAction';
|
||||||
import CreateWizard from './CreateWizard';
|
import CreateWizard from './CreateWizard';
|
||||||
|
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default class CreateAction extends PropertiesAction {
|
export default class CreateAction extends PropertiesAction {
|
||||||
constructor(openmct, type, parentDomainObject) {
|
constructor(openmct, type, parentDomainObject) {
|
||||||
|
@ -85,14 +85,13 @@
|
|||||||
class="c-dial__bg"
|
class="c-dial__bg"
|
||||||
viewBox="0 0 10 10"
|
viewBox="0 0 10 10"
|
||||||
>
|
>
|
||||||
|
|
||||||
<g
|
<g
|
||||||
v-if="limitLow !== null && dialLowLimitDeg < getLimitDegree('low', 'max')"
|
v-if="isDialLowLimit"
|
||||||
class="c-dial__limit-low"
|
class="c-dial__limit-low"
|
||||||
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
:style="`transform: rotate(${dialLowLimitDeg}deg)`"
|
||||||
>
|
>
|
||||||
<rect
|
<rect
|
||||||
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q1')"
|
v-if="isDialLowLimitLow"
|
||||||
class="c-dial__low-limit__low"
|
class="c-dial__low-limit__low"
|
||||||
x="5"
|
x="5"
|
||||||
y="5"
|
y="5"
|
||||||
@ -100,7 +99,7 @@
|
|||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q2')"
|
v-if="isDialLowLimitMid"
|
||||||
class="c-dial__low-limit__mid"
|
class="c-dial__low-limit__mid"
|
||||||
x="5"
|
x="5"
|
||||||
y="0"
|
y="0"
|
||||||
@ -108,7 +107,7 @@
|
|||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-if="dialLowLimitDeg >= getLimitDegree('low', 'q3')"
|
v-if="isDialLowLimitHigh"
|
||||||
class="c-dial__low-limit__high"
|
class="c-dial__low-limit__high"
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
@ -118,12 +117,12 @@
|
|||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g
|
<g
|
||||||
v-if="limitHigh !== null && dialHighLimitDeg < getLimitDegree('high', 'max')"
|
v-if="isDialHighLimit"
|
||||||
class="c-dial__limit-high"
|
class="c-dial__limit-high"
|
||||||
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
:style="`transform: rotate(${dialHighLimitDeg}deg)`"
|
||||||
>
|
>
|
||||||
<rect
|
<rect
|
||||||
v-if="dialHighLimitDeg <= getLimitDegree('high', 'max')"
|
v-if="isDialHighLimitLow"
|
||||||
class="c-dial__high-limit__low"
|
class="c-dial__high-limit__low"
|
||||||
x="0"
|
x="0"
|
||||||
y="5"
|
y="5"
|
||||||
@ -131,7 +130,7 @@
|
|||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-if="dialHighLimitDeg <= getLimitDegree('high', 'q2')"
|
v-if="isDialHighLimitMid"
|
||||||
class="c-dial__high-limit__mid"
|
class="c-dial__high-limit__mid"
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
@ -139,7 +138,7 @@
|
|||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-if="dialHighLimitDeg <= getLimitDegree('high', 'q3')"
|
v-if="isDialHighLimitHigh"
|
||||||
class="c-dial__high-limit__high"
|
class="c-dial__high-limit__high"
|
||||||
x="5"
|
x="5"
|
||||||
y="0"
|
y="0"
|
||||||
@ -159,7 +158,7 @@
|
|||||||
:style="`transform: rotate(${degValueFilledDial}deg)`"
|
:style="`transform: rotate(${degValueFilledDial}deg)`"
|
||||||
>
|
>
|
||||||
<rect
|
<rect
|
||||||
v-if="degValue >= getLimitDegree('low', 'q1')"
|
v-if="isDialFilledValueLow"
|
||||||
class="c-dial__filled-value__low"
|
class="c-dial__filled-value__low"
|
||||||
x="5"
|
x="5"
|
||||||
y="5"
|
y="5"
|
||||||
@ -167,7 +166,7 @@
|
|||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-if="degValue >= getLimitDegree('low', 'q2')"
|
v-if="isDialFilledValueMid"
|
||||||
class="c-dial__filled-value__mid"
|
class="c-dial__filled-value__mid"
|
||||||
x="5"
|
x="5"
|
||||||
y="0"
|
y="0"
|
||||||
@ -175,7 +174,7 @@
|
|||||||
height="5"
|
height="5"
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-if="degValue >= getLimitDegree('low', 'q3')"
|
v-if="isDialFilledValueHigh"
|
||||||
class="c-dial__filled-value__high"
|
class="c-dial__filled-value__high"
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
@ -216,13 +215,13 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="limitHigh !== null && meterHighLimitPerc > 0"
|
v-if="isMeterLimitHigh"
|
||||||
class="c-meter__limit-high"
|
class="c-meter__limit-high"
|
||||||
:style="`height: ${meterHighLimitPerc}%`"
|
:style="`height: ${meterHighLimitPerc}%`"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="limitLow !== null && meterLowLimitPerc > 0"
|
v-if="isMeterLimitLow"
|
||||||
class="c-meter__limit-low"
|
class="c-meter__limit-low"
|
||||||
:style="`height: ${meterLowLimitPerc}%`"
|
:style="`height: ${meterLowLimitPerc}%`"
|
||||||
></div>
|
></div>
|
||||||
@ -235,13 +234,13 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="limitHigh !== null && meterHighLimitPerc > 0"
|
v-if="isMeterLimitHigh"
|
||||||
class="c-meter__limit-high"
|
class="c-meter__limit-high"
|
||||||
:style="`width: ${meterHighLimitPerc}%`"
|
:style="`width: ${meterHighLimitPerc}%`"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="limitLow !== null && meterLowLimitPerc > 0"
|
v-if="isMeterLimitLow"
|
||||||
class="c-meter__limit-low"
|
class="c-meter__limit-low"
|
||||||
:style="`width: ${meterLowLimitPerc}%`"
|
:style="`width: ${meterLowLimitPerc}%`"
|
||||||
></div>
|
></div>
|
||||||
@ -275,6 +274,7 @@
|
|||||||
import { DIAL_VALUE_DEG_OFFSET, getLimitDegree } from '../gauge-limit-util';
|
import { DIAL_VALUE_DEG_OFFSET, getLimitDegree } from '../gauge-limit-util';
|
||||||
|
|
||||||
const LIMIT_PADDING_IN_PERCENT = 10;
|
const LIMIT_PADDING_IN_PERCENT = 10;
|
||||||
|
const DEFAULT_CURRENT_VALUE = '--';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Gauge',
|
name: 'Gauge',
|
||||||
@ -283,7 +283,7 @@ export default {
|
|||||||
let gaugeController = this.domainObject.configuration.gaugeController;
|
let gaugeController = this.domainObject.configuration.gaugeController;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
curVal: 0,
|
curVal: DEFAULT_CURRENT_VALUE,
|
||||||
digits: 3,
|
digits: 3,
|
||||||
precision: gaugeController.precision,
|
precision: gaugeController.precision,
|
||||||
displayMinMax: gaugeController.isDisplayMinMax,
|
displayMinMax: gaugeController.isDisplayMinMax,
|
||||||
@ -319,6 +319,45 @@ export default {
|
|||||||
|
|
||||||
return VIEWBOX_STR.replace('X', this.digits * DIGITS_RATIO);
|
return VIEWBOX_STR.replace('X', this.digits * DIGITS_RATIO);
|
||||||
},
|
},
|
||||||
|
isDialLowLimit() {
|
||||||
|
return this.limitLow.length > 0 && this.dialLowLimitDeg < getLimitDegree('low', 'max');
|
||||||
|
},
|
||||||
|
isDialLowLimitLow() {
|
||||||
|
return this.dialLowLimitDeg >= getLimitDegree('low', 'q1');
|
||||||
|
},
|
||||||
|
isDialLowLimitMid() {
|
||||||
|
return this.dialLowLimitDeg >= getLimitDegree('low', 'q2');
|
||||||
|
},
|
||||||
|
isDialLowLimitHigh() {
|
||||||
|
return this.dialLowLimitDeg >= getLimitDegree('low', 'q3');
|
||||||
|
},
|
||||||
|
isDialHighLimit() {
|
||||||
|
return this.limitHigh.length > 0 && this.dialHighLimitDeg < getLimitDegree('high', 'max');
|
||||||
|
},
|
||||||
|
isDialHighLimitLow() {
|
||||||
|
return this.dialHighLimitDeg <= getLimitDegree('high', 'max');
|
||||||
|
},
|
||||||
|
isDialHighLimitMid() {
|
||||||
|
return this.dialHighLimitDeg <= getLimitDegree('high', 'q2');
|
||||||
|
},
|
||||||
|
isDialHighLimitHigh() {
|
||||||
|
return this.dialHighLimitDeg <= getLimitDegree('high', 'q3');
|
||||||
|
},
|
||||||
|
isDialFilledValueLow() {
|
||||||
|
return this.degValue >= getLimitDegree('low', 'q1');
|
||||||
|
},
|
||||||
|
isDialFilledValueMid() {
|
||||||
|
return this.degValue >= getLimitDegree('low', 'q2');
|
||||||
|
},
|
||||||
|
isDialFilledValueHigh() {
|
||||||
|
return this.degValue >= getLimitDegree('low', 'q3');
|
||||||
|
},
|
||||||
|
isMeterLimitHigh() {
|
||||||
|
return this.limitHigh.length > 0 && this.meterHighLimitPerc > 0;
|
||||||
|
},
|
||||||
|
isMeterLimitLow() {
|
||||||
|
return this.limitLow.length > 0 && this.meterLowLimitPerc > 0;
|
||||||
|
},
|
||||||
typeDial() {
|
typeDial() {
|
||||||
return this.matchGaugeType('dial');
|
return this.matchGaugeType('dial');
|
||||||
},
|
},
|
||||||
@ -459,13 +498,14 @@ export default {
|
|||||||
this.unsubscribe = null;
|
this.unsubscribe = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.metadata = null;
|
this.curVal = DEFAULT_CURRENT_VALUE;
|
||||||
this.formats = null;
|
this.formats = null;
|
||||||
this.valueKey = null;
|
this.limitHigh = '';
|
||||||
this.limitHigh = null;
|
this.limitLow = '';
|
||||||
this.limitLow = null;
|
this.metadata = null;
|
||||||
this.rangeHigh = null;
|
this.rangeHigh = null;
|
||||||
this.rangeLow = null;
|
this.rangeLow = null;
|
||||||
|
this.valueKey = null;
|
||||||
},
|
},
|
||||||
request(domainObject = this.telemetryObject) {
|
request(domainObject = this.telemetryObject) {
|
||||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||||
@ -518,13 +558,20 @@ export default {
|
|||||||
} else if (telemetryLimit.WATCH) {
|
} else if (telemetryLimit.WATCH) {
|
||||||
limits = telemetryLimit.WATCH;
|
limits = telemetryLimit.WATCH;
|
||||||
} else {
|
} else {
|
||||||
this.openmct.notifications.error('No limits definition for given telemetry');
|
this.openmct.notifications.error('No limits definition for given telemetry, hiding low and high limits');
|
||||||
|
this.displayMinMax = false;
|
||||||
|
this.limitHigh = '';
|
||||||
|
this.limitLow = '';
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.limitHigh = this.round(limits.high[this.valueKey]);
|
this.limitHigh = this.round(limits.high[this.valueKey]);
|
||||||
this.limitLow = this.round(limits.low[this.valueKey]);
|
this.limitLow = this.round(limits.low[this.valueKey]);
|
||||||
this.rangeHigh = this.round(this.limitHigh + this.limitHigh * LIMIT_PADDING_IN_PERCENT / 100);
|
this.rangeHigh = this.round(this.limitHigh + this.limitHigh * LIMIT_PADDING_IN_PERCENT / 100);
|
||||||
this.rangeLow = this.round(this.limitLow - Math.abs(this.limitLow * LIMIT_PADDING_IN_PERCENT / 100));
|
this.rangeLow = this.round(this.limitLow - Math.abs(this.limitLow * LIMIT_PADDING_IN_PERCENT / 100));
|
||||||
|
|
||||||
|
this.displayMinMax = this.domainObject.configuration.gaugeController.isDisplayMinMax;
|
||||||
},
|
},
|
||||||
updateValue(datum) {
|
updateValue(datum) {
|
||||||
this.datum = datum;
|
this.datum = datum;
|
||||||
|
@ -107,7 +107,10 @@ export default {
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
imageUrl: String
|
imageUrl: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="zoomFactor > 1"
|
v-if="zoomFactor > 1"
|
||||||
class="c-imagery__hints"
|
class="c-imagery__hints"
|
||||||
>{{formatImageAltText}}</div>
|
>{{ formatImageAltText }}</div>
|
||||||
<div
|
<div
|
||||||
ref="focusedImageWrapper"
|
ref="focusedImageWrapper"
|
||||||
class="image-wrapper"
|
class="image-wrapper"
|
||||||
|
@ -46,7 +46,6 @@ export default {
|
|||||||
|
|
||||||
// kickoff
|
// kickoff
|
||||||
this.subscribe();
|
this.subscribe();
|
||||||
this.requestHistory();
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.unsubscribe) {
|
if (this.unsubscribe) {
|
||||||
@ -169,8 +168,6 @@ export default {
|
|||||||
// splice array to encourage garbage collection
|
// splice array to encourage garbage collection
|
||||||
this.imageHistory.splice(0, this.imageHistory.length);
|
this.imageHistory.splice(0, this.imageHistory.length);
|
||||||
|
|
||||||
// requesting history effectively clears imageHistory array
|
|
||||||
return this.requestHistory();
|
|
||||||
},
|
},
|
||||||
timeSystemChange() {
|
timeSystemChange() {
|
||||||
this.timeSystem = this.timeContext.timeSystem();
|
this.timeSystem = this.timeContext.timeSystem();
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import objectUtils from 'objectUtils';
|
import objectUtils from 'objectUtils';
|
||||||
import uuid from "uuid";
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default class ImportAsJSONAction {
|
export default class ImportAsJSONAction {
|
||||||
constructor(openmct) {
|
constructor(openmct) {
|
||||||
|
@ -177,7 +177,7 @@ export default {
|
|||||||
SearchResults,
|
SearchResults,
|
||||||
Sidebar
|
Sidebar
|
||||||
},
|
},
|
||||||
inject: ['openmct', 'snapshotContainer'],
|
inject: ['agent', 'openmct', 'snapshotContainer'],
|
||||||
props: {
|
props: {
|
||||||
domainObject: {
|
domainObject: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@ -455,12 +455,9 @@ export default {
|
|||||||
- tablet portrait
|
- tablet portrait
|
||||||
- in a layout frame (within .c-so-view)
|
- in a layout frame (within .c-so-view)
|
||||||
*/
|
*/
|
||||||
const classList = document.querySelector('body').classList;
|
const isPhone = this.agent.isPhone();
|
||||||
const isPhone = Array.from(classList).includes('phone');
|
const isTablet = this.agent.isTablet();
|
||||||
const isTablet = Array.from(classList).includes('tablet');
|
const isPortrait = this.agent.isPortrait();
|
||||||
// address in https://github.com/nasa/openmct/issues/4875
|
|
||||||
// eslint-disable-next-line compat/compat
|
|
||||||
const isPortrait = window.screen.orientation.type.includes('portrait');
|
|
||||||
const isInLayout = Boolean(this.$el.closest('.c-so-view'));
|
const isInLayout = Boolean(this.$el.closest('.c-so-view'));
|
||||||
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
|
const sidebarCoversEntries = (isPhone || (isTablet && isPortrait) || isInLayout);
|
||||||
this.sidebarCoversEntries = sidebarCoversEntries;
|
this.sidebarCoversEntries = sidebarCoversEntries;
|
||||||
|
@ -69,7 +69,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import SectionCollection from './SectionCollection.vue';
|
import SectionCollection from './SectionCollection.vue';
|
||||||
import PageCollection from './PageCollection.vue';
|
import PageCollection from './PageCollection.vue';
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -8,6 +8,7 @@ import { notebookImageMigration, IMAGE_MIGRATION_VER } from '../notebook/utils/n
|
|||||||
import { NOTEBOOK_TYPE } from './notebook-constants';
|
import { NOTEBOOK_TYPE } from './notebook-constants';
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import Agent from '@/utils/agent/Agent';
|
||||||
|
|
||||||
export default function NotebookPlugin() {
|
export default function NotebookPlugin() {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@ -18,7 +19,7 @@ export default function NotebookPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openmct.actions.register(new CopyToNotebookAction(openmct));
|
openmct.actions.register(new CopyToNotebookAction(openmct));
|
||||||
|
const agent = new Agent(window);
|
||||||
const notebookType = {
|
const notebookType = {
|
||||||
name: 'Notebook',
|
name: 'Notebook',
|
||||||
description: 'Create and save timestamped notes with embedded object snapshots.',
|
description: 'Create and save timestamped notes with embedded object snapshots.',
|
||||||
@ -142,6 +143,7 @@ export default function NotebookPlugin() {
|
|||||||
Notebook
|
Notebook
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
|
agent,
|
||||||
openmct,
|
openmct,
|
||||||
snapshotContainer
|
snapshotContainer
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export const DEFAULT_SIZE = {
|
export const DEFAULT_SIZE = {
|
||||||
width: 30,
|
width: 30,
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import PlanActivityView from "./PlanActivityView.vue";
|
import PlanActivityView from "./PlanActivityView.vue";
|
||||||
import { getPreciseDuration } from "utils/duration";
|
import { getPreciseDuration } from "utils/duration";
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const propertyLabels = {
|
const propertyLabels = {
|
||||||
'start': 'Start DateTime',
|
'start': 'Start DateTime',
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ActivityProperty from './ActivityProperty.vue';
|
import ActivityProperty from './ActivityProperty.vue';
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -154,6 +154,22 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="c-button-set c-button-set--strip-h">
|
||||||
|
<button
|
||||||
|
class="c-button icon-crosshair"
|
||||||
|
:class="{ 'is-active': cursorGuide }"
|
||||||
|
title="Toggle cursor guides"
|
||||||
|
@click="toggleCursorGuide"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="c-button"
|
||||||
|
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
|
||||||
|
title="Toggle grid lines"
|
||||||
|
@click="toggleGridLines"
|
||||||
|
>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--Cursor guides-->
|
<!--Cursor guides-->
|
||||||
@ -213,16 +229,16 @@ export default {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
gridLines: {
|
initGridLines: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cursorGuide: {
|
initCursorGuide: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default() {
|
default() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plotTickWidth: {
|
plotTickWidth: {
|
||||||
@ -252,7 +268,9 @@ export default {
|
|||||||
isTimeOutOfSync: false,
|
isTimeOutOfSync: false,
|
||||||
showLimitLineLabels: undefined,
|
showLimitLineLabels: undefined,
|
||||||
isFrozenOnMouseDown: false,
|
isFrozenOnMouseDown: false,
|
||||||
hasSameRangeValue: true
|
hasSameRangeValue: true,
|
||||||
|
cursorGuide: this.initCursorGuide,
|
||||||
|
gridLines: this.initGridLines
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -273,6 +291,14 @@ export default {
|
|||||||
return this.plotTickWidth || this.tickWidth;
|
return this.plotTickWidth || this.tickWidth;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
initGridLines(newGridLines) {
|
||||||
|
this.gridLines = newGridLines;
|
||||||
|
},
|
||||||
|
initCursorGuide(newCursorGuide) {
|
||||||
|
this.cursorGuide = newCursorGuide;
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
document.addEventListener('keyup', this.handleKeyUp);
|
document.addEventListener('keyup', this.handleKeyUp);
|
||||||
@ -1130,6 +1156,14 @@ export default {
|
|||||||
},
|
},
|
||||||
legendHoverChanged(data) {
|
legendHoverChanged(data) {
|
||||||
this.showLimitLineLabels = data;
|
this.showLimitLineLabels = data;
|
||||||
|
},
|
||||||
|
toggleCursorGuide() {
|
||||||
|
this.cursorGuide = !this.cursorGuide;
|
||||||
|
this.$emit('cursorGuide', this.cursorGuide);
|
||||||
|
},
|
||||||
|
toggleGridLines() {
|
||||||
|
this.gridLines = !this.gridLines;
|
||||||
|
this.$emit('gridLines', this.gridLines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -24,41 +24,6 @@
|
|||||||
ref="plotWrapper"
|
ref="plotWrapper"
|
||||||
class="c-plot holder holder-plot has-control-bar"
|
class="c-plot holder holder-plot has-control-bar"
|
||||||
>
|
>
|
||||||
<div
|
|
||||||
v-if="!options.compact"
|
|
||||||
class="c-control-bar"
|
|
||||||
>
|
|
||||||
<span class="c-button-set c-button-set--strip-h">
|
|
||||||
<button
|
|
||||||
class="c-button icon-download"
|
|
||||||
title="Export This View's Data as PNG"
|
|
||||||
@click="exportPNG()"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">PNG</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="c-button"
|
|
||||||
title="Export This View's Data as JPG"
|
|
||||||
@click="exportJPG()"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">JPG</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
class="c-button icon-crosshair"
|
|
||||||
:class="{ 'is-active': cursorGuide }"
|
|
||||||
title="Toggle cursor guides"
|
|
||||||
@click="toggleCursorGuide"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="c-button"
|
|
||||||
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
|
|
||||||
title="Toggle grid lines"
|
|
||||||
@click="toggleGridLines"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="plotContainer"
|
ref="plotContainer"
|
||||||
@ -70,8 +35,8 @@
|
|||||||
class="c-loading--overlay loading"
|
class="c-loading--overlay loading"
|
||||||
></div>
|
></div>
|
||||||
<mct-plot
|
<mct-plot
|
||||||
:grid-lines="gridLines"
|
:init-grid-lines="gridLines"
|
||||||
:cursor-guide="cursorGuide"
|
:init-cursor-guide="cursorGuide"
|
||||||
:options="options"
|
:options="options"
|
||||||
@loadingUpdated="loadingUpdated"
|
@loadingUpdated="loadingUpdated"
|
||||||
@statusUpdated="setStatus"
|
@statusUpdated="setStatus"
|
||||||
@ -135,15 +100,14 @@ export default {
|
|||||||
this.imageExporter.exportPNG(plotElement, 'plot.png', 'export-plot');
|
this.imageExporter.exportPNG(plotElement, 'plot.png', 'export-plot');
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleCursorGuide() {
|
|
||||||
this.cursorGuide = !this.cursorGuide;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleGridLines() {
|
|
||||||
this.gridLines = !this.gridLines;
|
|
||||||
},
|
|
||||||
setStatus(status) {
|
setStatus(status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
},
|
||||||
|
getViewContext() {
|
||||||
|
return {
|
||||||
|
exportPNG: this.exportPNG,
|
||||||
|
exportJPG: this.exportJPG
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -80,9 +80,16 @@ export default function PlotViewProvider(openmct) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<plot :options="options"></plot>'
|
template: '<plot ref="plotComponent" :options="options"></plot>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getViewContext() {
|
||||||
|
if (!component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return component.$refs.plotComponent.getViewContext();
|
||||||
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
|
57
src/plugins/plot/actions/ViewActions.js
Normal file
57
src/plugins/plot/actions/ViewActions.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 {isPlotView} from "@/plugins/plot/actions/utils";
|
||||||
|
|
||||||
|
const exportPNG = {
|
||||||
|
name: 'Export as PNG',
|
||||||
|
key: 'export-as-png',
|
||||||
|
description: 'Export This View\'s Data as PNG',
|
||||||
|
cssClass: 'c-icon-button icon-download',
|
||||||
|
group: 'view',
|
||||||
|
invoke(objectPath, view) {
|
||||||
|
view.getViewContext().exportPNG();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportJPG = {
|
||||||
|
name: 'Export as JPG',
|
||||||
|
key: 'export-as-jpg',
|
||||||
|
description: 'Export This View\'s Data as JPG',
|
||||||
|
cssClass: 'c-icon-button icon-download',
|
||||||
|
group: 'view',
|
||||||
|
invoke(objectPath, view) {
|
||||||
|
view.getViewContext().exportJPG();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewActions = [
|
||||||
|
exportPNG,
|
||||||
|
exportJPG
|
||||||
|
];
|
||||||
|
|
||||||
|
viewActions.forEach(action => {
|
||||||
|
action.appliesTo = (objectPath, view = {}) => {
|
||||||
|
return isPlotView(view);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export default viewActions;
|
3
src/plugins/plot/actions/utils.js
Normal file
3
src/plugins/plot/actions/utils.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export function isPlotView(view) {
|
||||||
|
return view.key === 'plot-single' || view.key === 'plot-overlay' || view.key === 'plot-stacked';
|
||||||
|
}
|
@ -65,9 +65,16 @@ export default function OverlayPlotViewProvider(openmct) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<plot :options="options"></plot>'
|
template: '<plot ref="plotComponent" :options="options"></plot>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getViewContext() {
|
||||||
|
if (!component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return component.$refs.plotComponent.getViewContext();
|
||||||
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
|
@ -25,6 +25,7 @@ import StackedPlotViewProvider from './stackedPlot/StackedPlotViewProvider';
|
|||||||
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
import PlotsInspectorViewProvider from './inspector/PlotsInspectorViewProvider';
|
||||||
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPolicy';
|
||||||
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
||||||
|
import PlotViewActions from "./actions/ViewActions";
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@ -67,6 +68,9 @@ export default function () {
|
|||||||
|
|
||||||
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
openmct.composition.addPolicy(new OverlayPlotCompositionPolicy(openmct).allow);
|
||||||
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
openmct.composition.addPolicy(new StackedPlotCompositionPolicy(openmct).allow);
|
||||||
|
|
||||||
|
PlotViewActions.forEach(action => {
|
||||||
|
openmct.actions.register(action);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,16 +724,16 @@ describe("the plugin", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("turns on cursor Guides all telemetry objects", (done) => {
|
it("turns on cursor Guides all telemetry objects", (done) => {
|
||||||
expect(plotViewComponentObject.cursorGuide).toBeFalse();
|
expect(plotViewComponentObject.$children[0].cursorGuide).toBeFalse();
|
||||||
plotViewComponentObject.toggleCursorGuide();
|
plotViewComponentObject.$children[0].cursorGuide = true;
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(plotViewComponentObject.$children[0].component.$children[0].cursorGuide).toBeTrue();
|
expect(plotViewComponentObject.$children[0].cursorGuide).toBeTrue();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows grid lines for all telemetry objects", () => {
|
it("shows grid lines for all telemetry objects", () => {
|
||||||
expect(plotViewComponentObject.gridLines).toBeTrue();
|
expect(plotViewComponentObject.$children[0].gridLines).toBeTrue();
|
||||||
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
|
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
|
||||||
let visible = 0;
|
let visible = 0;
|
||||||
gridLinesContainer.forEach(el => {
|
gridLinesContainer.forEach(el => {
|
||||||
@ -745,10 +745,10 @@ describe("the plugin", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("hides grid lines for all telemetry objects", (done) => {
|
it("hides grid lines for all telemetry objects", (done) => {
|
||||||
expect(plotViewComponentObject.gridLines).toBeTrue();
|
expect(plotViewComponentObject.$children[0].gridLines).toBeTrue();
|
||||||
plotViewComponentObject.toggleGridLines();
|
plotViewComponentObject.$children[0].gridLines = false;
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(plotViewComponentObject.gridLines).toBeFalse();
|
expect(plotViewComponentObject.$children[0].gridLines).toBeFalse();
|
||||||
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
|
let gridLinesContainer = element.querySelectorAll(".gl-plot-display-area .js-ticks");
|
||||||
let visible = 0;
|
let visible = 0;
|
||||||
gridLinesContainer.forEach(el => {
|
gridLinesContainer.forEach(el => {
|
||||||
|
@ -22,41 +22,6 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-plot c-plot--stacked holder holder-plot has-control-bar">
|
<div class="c-plot c-plot--stacked holder holder-plot has-control-bar">
|
||||||
<div
|
|
||||||
v-show="!hideExportButtons && !options.compact"
|
|
||||||
class="c-control-bar"
|
|
||||||
>
|
|
||||||
<span class="c-button-set c-button-set--strip-h">
|
|
||||||
<button
|
|
||||||
class="c-button icon-download"
|
|
||||||
title="Export This View's Data as PNG"
|
|
||||||
@click="exportPNG()"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">PNG</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="c-button"
|
|
||||||
title="Export This View's Data as JPG"
|
|
||||||
@click="exportJPG()"
|
|
||||||
>
|
|
||||||
<span class="c-button__label">JPG</span>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
class="c-button icon-crosshair"
|
|
||||||
:class="{ 'is-active': cursorGuide }"
|
|
||||||
title="Toggle cursor guides"
|
|
||||||
@click="toggleCursorGuide"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="c-button"
|
|
||||||
:class="{ 'icon-grid-on': gridLines, 'icon-grid-off': !gridLines }"
|
|
||||||
title="Toggle grid lines"
|
|
||||||
@click="toggleGridLines"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="l-view-section">
|
<div class="l-view-section">
|
||||||
<stacked-plot-item
|
<stacked-plot-item
|
||||||
v-for="object in compositionObjects"
|
v-for="object in compositionObjects"
|
||||||
@ -69,6 +34,8 @@
|
|||||||
:plot-tick-width="maxTickWidth"
|
:plot-tick-width="maxTickWidth"
|
||||||
@plotTickWidth="onTickWidthChange"
|
@plotTickWidth="onTickWidthChange"
|
||||||
@loadingUpdated="loadingUpdated"
|
@loadingUpdated="loadingUpdated"
|
||||||
|
@cursorGuide="onCursorGuideChange"
|
||||||
|
@gridLines="onGridLinesChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,20 +151,24 @@ export default {
|
|||||||
this.hideExportButtons = false;
|
this.hideExportButtons = false;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleCursorGuide() {
|
|
||||||
this.cursorGuide = !this.cursorGuide;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleGridLines() {
|
|
||||||
this.gridLines = !this.gridLines;
|
|
||||||
},
|
|
||||||
onTickWidthChange(width, plotId) {
|
onTickWidthChange(width, plotId) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(this.tickWidthMap, plotId)) {
|
if (!Object.prototype.hasOwnProperty.call(this.tickWidthMap, plotId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$set(this.tickWidthMap, plotId, width);
|
this.$set(this.tickWidthMap, plotId, width);
|
||||||
|
},
|
||||||
|
onCursorGuideChange(cursorGuide) {
|
||||||
|
this.cursorGuide = cursorGuide === true;
|
||||||
|
},
|
||||||
|
onGridLinesChange(gridLines) {
|
||||||
|
this.gridLines = gridLines === true;
|
||||||
|
},
|
||||||
|
getViewContext() {
|
||||||
|
return {
|
||||||
|
exportPNG: this.exportPNG,
|
||||||
|
exportJPG: this.exportJPG
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -96,6 +96,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onTickWidthChange = this.onTickWidthChange;
|
const onTickWidthChange = this.onTickWidthChange;
|
||||||
|
const onCursorGuideChange = this.onCursorGuideChange;
|
||||||
|
const onGridLinesChange = this.onGridLinesChange;
|
||||||
const loadingUpdated = this.loadingUpdated;
|
const loadingUpdated = this.loadingUpdated;
|
||||||
const setStatus = this.setStatus;
|
const setStatus = this.setStatus;
|
||||||
|
|
||||||
@ -121,16 +123,24 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...getProps(),
|
...getProps(),
|
||||||
onTickWidthChange,
|
onTickWidthChange,
|
||||||
|
onCursorGuideChange,
|
||||||
|
onGridLinesChange,
|
||||||
loadingUpdated,
|
loadingUpdated,
|
||||||
setStatus
|
setStatus
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :grid-lines="gridLines" :cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
|
template: '<div ref="plotWrapper" class="l-view-section u-style-receiver js-style-receiver" :class="{\'s-status-timeconductor-unsynced\': status && status === \'timeconductor-unsynced\'}"><div v-show="!!loading" class="c-loading--overlay loading"></div><mct-plot :init-grid-lines="gridLines" :init-cursor-guide="cursorGuide" :plot-tick-width="plotTickWidth" :options="options" @plotTickWidth="onTickWidthChange" @cursorGuide="onCursorGuideChange" @gridLines="onGridLinesChange" @statusUpdated="setStatus" @loadingUpdated="loadingUpdated"/></div>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onTickWidthChange() {
|
onTickWidthChange() {
|
||||||
this.$emit('plotTickWidth', ...arguments);
|
this.$emit('plotTickWidth', ...arguments);
|
||||||
},
|
},
|
||||||
|
onCursorGuideChange() {
|
||||||
|
this.$emit('cursorGuide', ...arguments);
|
||||||
|
},
|
||||||
|
onGridLinesChange() {
|
||||||
|
this.$emit('gridLines', ...arguments);
|
||||||
|
},
|
||||||
setStatus(status) {
|
setStatus(status) {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.updateComponentProp('status', status);
|
this.updateComponentProp('status', status);
|
||||||
|
@ -67,9 +67,16 @@ export default function StackedPlotViewProvider(openmct) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<stacked-plot :options="options"></stacked-plot>'
|
template: '<stacked-plot ref="plotComponent" :options="options"></stacked-plot>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getViewContext() {
|
||||||
|
if (!component) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return component.$refs.plotComponent.getViewContext();
|
||||||
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
ref="startOffset"
|
ref="startOffset"
|
||||||
class="c-button c-conductor__delta-button"
|
class="c-button c-conductor__delta-button"
|
||||||
title="Set the time offset after now"
|
title="Set the time offset after now"
|
||||||
|
data-testid="conductor-start-offset-button"
|
||||||
@click.prevent.stop="showTimePopupStart"
|
@click.prevent.stop="showTimePopupStart"
|
||||||
>
|
>
|
||||||
{{ offsets.start }}
|
{{ offsets.start }}
|
||||||
@ -61,6 +62,7 @@
|
|||||||
ref="endOffset"
|
ref="endOffset"
|
||||||
class="c-button c-conductor__delta-button"
|
class="c-button c-conductor__delta-button"
|
||||||
title="Set the time offset preceding now"
|
title="Set the time offset preceding now"
|
||||||
|
data-testid="conductor-end-offset-button"
|
||||||
@click.prevent.stop="showTimePopupEnd"
|
@click.prevent.stop="showTimePopupEnd"
|
||||||
>
|
>
|
||||||
{{ offsets.end }}
|
{{ offsets.end }}
|
||||||
|
@ -105,6 +105,7 @@ export default {
|
|||||||
name: 'Fixed Timespan',
|
name: 'Fixed Timespan',
|
||||||
description: 'Query and explore data that falls between two fixed datetimes.',
|
description: 'Query and explore data that falls between two fixed datetimes.',
|
||||||
cssClass: 'icon-tabular',
|
cssClass: 'icon-tabular',
|
||||||
|
testId: 'conductor-modeOption-fixed',
|
||||||
onItemClicked: () => this.setOption(key)
|
onItemClicked: () => this.setOption(key)
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -116,6 +117,7 @@ export default {
|
|||||||
description: "Monitor streaming data in real-time. The Time "
|
description: "Monitor streaming data in real-time. The Time "
|
||||||
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
+ "Conductor and displays will automatically advance themselves based on this clock. " + clock.description,
|
||||||
cssClass: clock.cssClass || 'icon-clock',
|
cssClass: clock.cssClass || 'icon-clock',
|
||||||
|
testId: 'conductor-modeOption-realtime',
|
||||||
onItemClicked: () => this.setOption(key)
|
onItemClicked: () => this.setOption(key)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -148,7 +150,8 @@ export default {
|
|||||||
if (clockKey === undefined) {
|
if (clockKey === undefined) {
|
||||||
this.openmct.time.stopClock();
|
this.openmct.time.stopClock();
|
||||||
} else {
|
} else {
|
||||||
this.openmct.time.clock(clockKey, configuration.clockOffsets);
|
const offsets = this.openmct.time.clockOffsets() || configuration.clockOffsets;
|
||||||
|
this.openmct.time.clock(clockKey, offsets);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ import ticker from 'utils/clock/Ticker';
|
|||||||
import {SORT_ORDER_OPTIONS} from "./constants";
|
import {SORT_ORDER_OPTIONS} from "./constants";
|
||||||
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import uuid from "uuid";
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const SCROLL_TIMEOUT = 10000;
|
const SCROLL_TIMEOUT = 10000;
|
||||||
const ROW_HEIGHT = 30;
|
const ROW_HEIGHT = 30;
|
||||||
|
@ -36,10 +36,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__value {
|
|
||||||
color: $colorBodyFgEm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.c-frame & {
|
.c-frame & {
|
||||||
// When in a Display or Flexible Layout
|
// When in a Display or Flexible Layout
|
||||||
@include abs();
|
@include abs();
|
||||||
@ -66,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__direction {
|
&__direction {
|
||||||
font-size: 0.9rem !important;
|
font-size: 0.7em !important;
|
||||||
margin-right: $interiorMargin;
|
margin-right: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,21 @@ export default class Agent {
|
|||||||
* @returns {boolean} true in portrait mode
|
* @returns {boolean} true in portrait mode
|
||||||
*/
|
*/
|
||||||
isPortrait() {
|
isPortrait() {
|
||||||
return this.window.innerWidth < this.window.innerHeight;
|
const { screen } = this.window;
|
||||||
|
const hasScreenOrientation = screen && Object.prototype.hasOwnProperty.call(screen, 'orientation');
|
||||||
|
const hasWindowOrientation = Object.prototype.hasOwnProperty.call(this.window, 'orientation');
|
||||||
|
|
||||||
|
if (hasScreenOrientation) {
|
||||||
|
return screen.orientation.type.includes('portrait');
|
||||||
|
} else if (hasWindowOrientation) {
|
||||||
|
// Use window.orientation API if available (e.g. Safari mobile)
|
||||||
|
// which returns [-90, 0, 90, 180] based on device orientation.
|
||||||
|
const { orientation } = this.window;
|
||||||
|
|
||||||
|
return Math.abs(orientation / 90) % 2 === 0;
|
||||||
|
} else {
|
||||||
|
return this.window.innerWidth < this.window.innerHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Check if the user's device is in a landscape-style
|
* Check if the user's device is in a landscape-style
|
||||||
|
@ -68,7 +68,7 @@ describe("The Agent", function () {
|
|||||||
expect(agent.isTablet()).toBeTruthy();
|
expect(agent.isTablet()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("detects display orientation", function () {
|
it("detects display orientation by innerHeight and innerWidth", function () {
|
||||||
agent = new Agent(testWindow);
|
agent = new Agent(testWindow);
|
||||||
testWindow.innerWidth = 1024;
|
testWindow.innerWidth = 1024;
|
||||||
testWindow.innerHeight = 400;
|
testWindow.innerHeight = 400;
|
||||||
@ -80,6 +80,34 @@ describe("The Agent", function () {
|
|||||||
expect(agent.isLandscape()).toBeFalsy();
|
expect(agent.isLandscape()).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("detects display orientation by screen.orientation", function () {
|
||||||
|
agent = new Agent(testWindow);
|
||||||
|
testWindow.screen = {
|
||||||
|
orientation: {
|
||||||
|
type: "landscape-primary"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(agent.isPortrait()).toBeFalsy();
|
||||||
|
expect(agent.isLandscape()).toBeTruthy();
|
||||||
|
testWindow.screen = {
|
||||||
|
orientation: {
|
||||||
|
type: "portrait-primary"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
expect(agent.isPortrait()).toBeTruthy();
|
||||||
|
expect(agent.isLandscape()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects display orientation by window.orientation", function () {
|
||||||
|
agent = new Agent(testWindow);
|
||||||
|
testWindow.orientation = 90;
|
||||||
|
expect(agent.isPortrait()).toBeFalsy();
|
||||||
|
expect(agent.isLandscape()).toBeTruthy();
|
||||||
|
testWindow.orientation = 0;
|
||||||
|
expect(agent.isPortrait()).toBeTruthy();
|
||||||
|
expect(agent.isLandscape()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
it("detects touch support", function () {
|
it("detects touch support", function () {
|
||||||
testWindow.ontouchstart = null;
|
testWindow.ontouchstart = null;
|
||||||
expect(new Agent(testWindow).isTouch()).toBe(true);
|
expect(new Agent(testWindow).isTouch()).toBe(true);
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import uuid from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -26,9 +26,10 @@ const config = {
|
|||||||
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
|
maelstromTheme: './src/plugins/themes/maelstrom-theme.scss'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
globalObject: "this",
|
globalObject: 'this',
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
library: '[name]',
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: 'openmct',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
publicPath: '',
|
publicPath: '',
|
||||||
hashFunction: 'xxhash64',
|
hashFunction: 'xxhash64',
|
||||||
|
@ -16,5 +16,5 @@ module.exports = merge(common, {
|
|||||||
__OPENMCT_ROOT_RELATIVE__: '""'
|
__OPENMCT_ROOT_RELATIVE__: '""'
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
devtool: 'source-map'
|
devtool: 'eval-source-map'
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user