mirror of
https://github.com/nasa/openmct.git
synced 2025-01-13 00:09:47 +00:00
Merge branch 'master' into eval-source-maps
This commit is contained in:
commit
cc58dbd5e7
@ -5,18 +5,19 @@ orbs:
|
|||||||
executors:
|
executors:
|
||||||
pw-focal-development:
|
pw-focal-development:
|
||||||
docker:
|
docker:
|
||||||
- image: mcr.microsoft.com/playwright:v1.39.0-focal
|
- image: mcr.microsoft.com/playwright:v1.42.1-focal
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
|
||||||
PERCY_POSTINSTALL_BROWSER: "true" # Needed to store the percy browser in cache deps
|
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
|
||||||
PERCY_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
PERCY_LOGLEVEL: 'debug' # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||||
|
PERCY_PARALLEL_TOTAL: 2
|
||||||
ubuntu:
|
ubuntu:
|
||||||
machine:
|
machine:
|
||||||
image: ubuntu-2204:current
|
image: ubuntu-2204:current
|
||||||
docker_layer_caching: true
|
docker_layer_caching: true
|
||||||
commands:
|
commands:
|
||||||
build_and_install:
|
build_and_install:
|
||||||
description: "All steps used to build and install."
|
description: 'All steps used to build and install.'
|
||||||
parameters:
|
parameters:
|
||||||
node-version:
|
node-version:
|
||||||
type: string
|
type: string
|
||||||
@ -26,7 +27,7 @@ commands:
|
|||||||
node-version: << parameters.node-version >>
|
node-version: << parameters.node-version >>
|
||||||
- node/install-packages
|
- node/install-packages
|
||||||
generate_and_store_version_and_filesystem_artifacts:
|
generate_and_store_version_and_filesystem_artifacts:
|
||||||
description: "Track important packages and files"
|
description: 'Track important packages and files'
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
|
[[ $EUID -ne 0 ]] && (sudo mkdir -p /tmp/artifacts && sudo chmod 777 /tmp/artifacts) || (mkdir -p /tmp/artifacts && chmod 777 /tmp/artifacts)
|
||||||
@ -37,7 +38,7 @@ commands:
|
|||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/artifacts/
|
path: /tmp/artifacts/
|
||||||
generate_e2e_code_cov_report:
|
generate_e2e_code_cov_report:
|
||||||
description: "Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test"
|
description: 'Generate e2e code coverage artifacts and publish to codecov.io. Needed to that we can ignore the exit code status of the npm run test'
|
||||||
parameters:
|
parameters:
|
||||||
suite:
|
suite:
|
||||||
type: string
|
type: string
|
||||||
@ -101,7 +102,7 @@ jobs:
|
|||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||||
condition:
|
condition:
|
||||||
equal: ["full", <<parameters.suite>>]
|
equal: ['full', <<parameters.suite>>]
|
||||||
steps:
|
steps:
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run:
|
- run:
|
||||||
@ -158,7 +159,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/hydrogen
|
||||||
- run: npx playwright@1.39.0 install #Necessary for bare ubuntu machine
|
- run: npx playwright@1.42.1 install #Necessary for bare ubuntu machine
|
||||||
- run: |
|
- run: |
|
||||||
export $(cat src/plugins/persistence/couch/.env.ci | xargs)
|
export $(cat src/plugins/persistence/couch/.env.ci | xargs)
|
||||||
docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
|
docker-compose -f src/plugins/persistence/couch/couchdb-compose.yaml up --detach
|
||||||
@ -220,14 +221,15 @@ jobs:
|
|||||||
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
|
equal: [42, 42] # Always run codecov reports regardless of test failure https://discuss.circleci.com/t/make-custom-command-run-always-with-when-always/38957/2
|
||||||
steps:
|
steps:
|
||||||
- generate_and_store_version_and_filesystem_artifacts
|
- generate_and_store_version_and_filesystem_artifacts
|
||||||
visual-a11y-tests:
|
visual-a11y:
|
||||||
parameters:
|
parameters:
|
||||||
suite:
|
suite:
|
||||||
type: string # ci or full
|
type: string # ci or full
|
||||||
executor: pw-focal-development
|
executor: pw-focal-development
|
||||||
|
parallelism: 2
|
||||||
steps:
|
steps:
|
||||||
- build_and_install:
|
- build_and_install:
|
||||||
node-version: lts/hydrogen
|
node-version: lts/iron
|
||||||
- run: npm run test:e2e:visual:<<parameters.suite>>
|
- run: npm run test:e2e:visual:<<parameters.suite>>
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: test-results/results.xml
|
path: test-results/results.xml
|
||||||
@ -254,8 +256,8 @@ workflows:
|
|||||||
name: e2e-stable
|
name: e2e-stable
|
||||||
suite: stable
|
suite: stable
|
||||||
- e2e-mobile
|
- e2e-mobile
|
||||||
- visual-a11y-tests:
|
- visual-a11y:
|
||||||
name: visual-a11y-test-ci
|
name: visual-a11y-ci
|
||||||
suite: ci
|
suite: ci
|
||||||
|
|
||||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||||
@ -274,13 +276,13 @@ workflows:
|
|||||||
- e2e-mobile
|
- e2e-mobile
|
||||||
- perf-test
|
- perf-test
|
||||||
- mem-test
|
- mem-test
|
||||||
- visual-a11y-tests:
|
- visual-a11y:
|
||||||
name: visual-a11y-test-nightly
|
name: visual-a11y-nightly
|
||||||
suite: full
|
suite: full
|
||||||
- e2e-couchdb
|
- e2e-couchdb
|
||||||
triggers:
|
triggers:
|
||||||
- schedule:
|
- schedule:
|
||||||
cron: "0 0 * * *"
|
cron: '0 0 * * *'
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.42.1 install
|
||||||
|
|
||||||
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
- name: Start CouchDB Docker Container and Init with Setup Scripts
|
||||||
run: |
|
run: |
|
||||||
|
2
.github/workflows/e2e-flakefinder.yml
vendored
2
.github/workflows/e2e-flakefinder.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.42.1 install
|
||||||
- run: npm ci --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
|
|
||||||
- name: Run E2E Tests (Repeated 10 Times)
|
- name: Run E2E Tests (Repeated 10 Times)
|
||||||
|
2
.github/workflows/e2e-perf.yml
vendored
2
.github/workflows/e2e-perf.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.42.1 install
|
||||||
- run: npm ci --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
- run: npm run test:perf:localhost
|
- run: npm run test:perf:localhost
|
||||||
- run: npm run test:perf:contract
|
- run: npm run test:perf:contract
|
||||||
|
2
.github/workflows/e2e-pr.yml
vendored
2
.github/workflows/e2e-pr.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-node-
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
- run: npx playwright@1.39.0 install
|
- run: npx playwright@1.42.1 install
|
||||||
- run: npx playwright install chrome-beta
|
- run: npx playwright install chrome-beta
|
||||||
- run: npm ci --no-audit --progress=false
|
- run: npm ci --no-audit --progress=false
|
||||||
- run: npm run test:e2e:full -- --max-failures=40
|
- run: npm run test:e2e:full -- --max-failures=40
|
||||||
|
@ -16,8 +16,6 @@ The [CodeQL GitHub Actions workflow](https://github.com/nasa/openmct/blob/master
|
|||||||
|
|
||||||
CodeQL is run for every pull-request in GitHub Actions.
|
CodeQL is run for every pull-request in GitHub Actions.
|
||||||
|
|
||||||
The project is also monitored by [LGTM](https://lgtm.com/projects/g/nasa/openmct/) and is available to public.
|
|
||||||
|
|
||||||
### ESLint
|
### ESLint
|
||||||
|
|
||||||
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions.
|
Static analysis is run for every push on the master branch and every pull request on all branches in Github Actions.
|
||||||
|
@ -392,7 +392,8 @@ async function setTimeConductorMode(page, isFixedTimespan = true) {
|
|||||||
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
|
||||||
await page.waitForURL(/tc\.mode=local/);
|
await page.waitForURL(/tc\.mode=local/);
|
||||||
}
|
}
|
||||||
await page.getByLabel('Submit time offsets').or(page.getByLabel('Submit time bounds')).click();
|
//dismiss the time conductor popup
|
||||||
|
await page.getByLabel('Discard changes and close time popup').click();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -292,6 +292,16 @@ test.describe('Basic Condition Set Use', () => {
|
|||||||
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
|
await expect(page.getByRole('menuitem', { name: /Conditions View/ })).toBeVisible();
|
||||||
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
|
await expect(page.getByRole('menuitem', { name: /Plot/ })).toBeVisible();
|
||||||
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
|
await expect(page.getByRole('menuitem', { name: /Telemetry Table/ })).toBeVisible();
|
||||||
|
await page.getByLabel('Plot').click();
|
||||||
|
await expect(
|
||||||
|
page.getByLabel('Plot Legend Collapsed').getByText('Test Condition Set')
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
|
await page.getByLabel('Telemetry Table').click();
|
||||||
|
await expect(page.getByRole('searchbox', { name: 'output filter input' })).toBeVisible();
|
||||||
|
await page.getByLabel('Open the View Switcher Menu').click();
|
||||||
|
await page.getByLabel('Conditions View').click();
|
||||||
|
await expect(page.getByText('Current Output')).toBeVisible();
|
||||||
});
|
});
|
||||||
test('ConditionSet has correct outputs when telemetry is and is not available', async ({
|
test('ConditionSet has correct outputs when telemetry is and is not available', async ({
|
||||||
page
|
page
|
||||||
@ -457,4 +467,11 @@ test.describe('Basic Condition Set Use', () => {
|
|||||||
|
|
||||||
await page.goto(exampleTelemetry.url);
|
await page.goto(exampleTelemetry.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.fixme('Ensure condition sets work with telemetry like operator status', ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/7484'
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2024, 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 {
|
||||||
|
createDomainObjectWithDefaults,
|
||||||
|
setIndependentTimeConductorBounds
|
||||||
|
} from '../../../../appActions.js';
|
||||||
|
import { expect, test } from '../../../../pluginFixtures.js';
|
||||||
|
|
||||||
|
const FIXED_TIME =
|
||||||
|
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
|
||||||
|
test.describe('Datepicker operations', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(FIXED_TIME);
|
||||||
|
});
|
||||||
|
test('Verify that user can use the datepicker in the TC', async ({ page }) => {
|
||||||
|
await page.getByLabel('Time Conductor Mode').click();
|
||||||
|
// Click on the date picker that is left-most on the screen
|
||||||
|
await page.getByLabel('Global Time Conductor').locator('a').first().click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
// Click on the first cell
|
||||||
|
await page.getByText('27 239').click();
|
||||||
|
// Expect datepicker to close and time conductor date setting to be changed
|
||||||
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
test('Verify that user can use the datepicker in the ITC', async ({ page }) => {
|
||||||
|
const createdTimeList = await createDomainObjectWithDefaults(page, { type: 'Time List' });
|
||||||
|
|
||||||
|
await page.goto(createdTimeList.url, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
|
await setIndependentTimeConductorBounds(page, {
|
||||||
|
start: '2024-11-12 19:11:11.000Z',
|
||||||
|
end: '2024-11-12 20:11:11.000Z'
|
||||||
|
});
|
||||||
|
// Open ITC
|
||||||
|
await page.getByLabel('Start bounds').nth(0).click();
|
||||||
|
// Click on the datepicker icon
|
||||||
|
await page.locator('form a').first().click();
|
||||||
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
// Click on the first cell
|
||||||
|
await page.getByText('7 342').click();
|
||||||
|
// Expect datepicker to close and time conductor date setting to be changed
|
||||||
|
await expect(page.getByRole('dialog')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
});
|
@ -72,11 +72,29 @@ test.describe('Visual - Planning', () => {
|
|||||||
name: 'Plan Visual Test',
|
name: 'Plan Visual Test',
|
||||||
json: examplePlanSmall2
|
json: examplePlanSmall2
|
||||||
});
|
});
|
||||||
|
|
||||||
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
||||||
await percySnapshot(page, `Plan View (theme: ${theme})`);
|
await percySnapshot(page, `Plan View (theme: ${theme})`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Resize Plan View @2p', async ({ browser, theme }) => {
|
||||||
|
// need to set viewport to null to allow for resizing
|
||||||
|
const newContext = await browser.newContext({
|
||||||
|
viewport: null
|
||||||
|
});
|
||||||
|
const newPage = await newContext.newPage();
|
||||||
|
|
||||||
|
await newPage.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
const plan = await createPlanFromJSON(newPage, {
|
||||||
|
name: 'Plan Visual Test',
|
||||||
|
json: examplePlanSmall2
|
||||||
|
});
|
||||||
|
|
||||||
|
await setBoundsToSpanAllActivities(newPage, examplePlanSmall2, plan.url);
|
||||||
|
// resize the window
|
||||||
|
await newPage.setViewportSize({ width: 800, height: 600 });
|
||||||
|
await percySnapshot(newPage, `Plan View resized (theme: ${theme})`);
|
||||||
|
});
|
||||||
|
|
||||||
test('Plan View w/ draft status', async ({ page, theme }) => {
|
test('Plan View w/ draft status', async ({ page, theme }) => {
|
||||||
const plan = await createPlanFromJSON(page, {
|
const plan = await createPlanFromJSON(page, {
|
||||||
name: 'Plan Visual Test (Draft)',
|
name: 'Plan Visual Test (Draft)',
|
||||||
|
45
package-lock.json
generated
45
package-lock.json
generated
@ -14,14 +14,14 @@
|
|||||||
"@braintree/sanitize-url": "6.0.4",
|
"@braintree/sanitize-url": "6.0.4",
|
||||||
"@percy/cli": "1.27.4",
|
"@percy/cli": "1.27.4",
|
||||||
"@percy/playwright": "1.0.4",
|
"@percy/playwright": "1.0.4",
|
||||||
"@playwright/test": "1.39.0",
|
"@playwright/test": "1.42.1",
|
||||||
"@types/d3-axis": "3.0.6",
|
"@types/d3-axis": "3.0.6",
|
||||||
"@types/d3-scale": "4.0.8",
|
"@types/d3-scale": "4.0.8",
|
||||||
"@types/d3-selection": "3.0.10",
|
"@types/d3-selection": "3.0.10",
|
||||||
"@types/d3-shape": "3.0.0",
|
"@types/d3-shape": "3.0.0",
|
||||||
"@types/eventemitter3": "1.2.0",
|
"@types/eventemitter3": "1.2.0",
|
||||||
"@types/jasmine": "5.1.2",
|
"@types/jasmine": "5.1.2",
|
||||||
"@types/lodash": "4.14.192",
|
"@types/lodash": "4.17.0",
|
||||||
"@vue/compiler-sfc": "3.4.3",
|
"@vue/compiler-sfc": "3.4.3",
|
||||||
"babel-loader": "9.1.0",
|
"babel-loader": "9.1.0",
|
||||||
"babel-plugin-istanbul": "6.1.1",
|
"babel-plugin-istanbul": "6.1.1",
|
||||||
@ -1548,12 +1548,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.39.0",
|
"version": "1.42.1",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
|
||||||
"integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==",
|
"integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.39.0"
|
"playwright": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -1821,9 +1821,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.14.192",
|
"version": "4.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
||||||
"integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==",
|
"integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
@ -6027,9 +6027,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.5",
|
"version": "1.15.6",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -9026,12 +9026,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.39.0",
|
"version": "1.42.1",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
|
||||||
"integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==",
|
"integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.39.0"
|
"playwright-core": "1.42.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -9048,7 +9048,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
|
||||||
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
|
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright-core": "cli.js"
|
"playwright-core": "cli.js"
|
||||||
},
|
},
|
||||||
@ -9070,18 +9069,6 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright/node_modules/playwright-core": {
|
|
||||||
"version": "1.39.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz",
|
|
||||||
"integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"playwright-core": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/plotly.js-basic-dist-min": {
|
"node_modules/plotly.js-basic-dist-min": {
|
||||||
"version": "2.29.1",
|
"version": "2.29.1",
|
||||||
"resolved": "https://registry.npmjs.org/plotly.js-basic-dist-min/-/plotly.js-basic-dist-min-2.29.1.tgz",
|
"resolved": "https://registry.npmjs.org/plotly.js-basic-dist-min/-/plotly.js-basic-dist-min-2.29.1.tgz",
|
||||||
|
@ -10,14 +10,14 @@
|
|||||||
"@braintree/sanitize-url": "6.0.4",
|
"@braintree/sanitize-url": "6.0.4",
|
||||||
"@percy/cli": "1.27.4",
|
"@percy/cli": "1.27.4",
|
||||||
"@percy/playwright": "1.0.4",
|
"@percy/playwright": "1.0.4",
|
||||||
"@playwright/test": "1.39.0",
|
"@playwright/test": "1.42.1",
|
||||||
"@types/d3-axis": "3.0.6",
|
"@types/d3-axis": "3.0.6",
|
||||||
"@types/d3-shape": "3.0.0",
|
"@types/d3-shape": "3.0.0",
|
||||||
"@types/d3-scale": "4.0.8",
|
"@types/d3-scale": "4.0.8",
|
||||||
"@types/d3-selection": "3.0.10",
|
"@types/d3-selection": "3.0.10",
|
||||||
"@types/eventemitter3": "1.2.0",
|
"@types/eventemitter3": "1.2.0",
|
||||||
"@types/jasmine": "5.1.2",
|
"@types/jasmine": "5.1.2",
|
||||||
"@types/lodash": "4.14.192",
|
"@types/lodash": "4.17.0",
|
||||||
"@vue/compiler-sfc": "3.4.3",
|
"@vue/compiler-sfc": "3.4.3",
|
||||||
"babel-loader": "9.1.0",
|
"babel-loader": "9.1.0",
|
||||||
"babel-plugin-istanbul": "6.1.1",
|
"babel-plugin-istanbul": "6.1.1",
|
||||||
@ -156,4 +156,4 @@
|
|||||||
"keywords": [
|
"keywords": [
|
||||||
"nasa"
|
"nasa"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -56,20 +56,38 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeToTelemetry(endpoint) {
|
async requestLatestValue(endpoint) {
|
||||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
const options = {
|
||||||
if (this.subscriptions[id]) {
|
size: 1,
|
||||||
console.log('subscription already exists');
|
strategy: 'latest'
|
||||||
|
};
|
||||||
|
const latestData = await this.openmct.telemetry.request(endpoint, options);
|
||||||
|
|
||||||
|
if (!latestData) {
|
||||||
|
throw new Error('Telemetry request failed by returning a falsy response');
|
||||||
|
}
|
||||||
|
if (latestData.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.telemetryReceived(endpoint, latestData[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToTelemetry(endpoint) {
|
||||||
|
const telemetryKeyString = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||||
|
if (this.subscriptions[telemetryKeyString]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = this.openmct.telemetry.getMetadata(endpoint);
|
const metadata = this.openmct.telemetry.getMetadata(endpoint);
|
||||||
|
|
||||||
this.telemetryObjects[id] = Object.assign({}, endpoint, {
|
this.telemetryObjects[telemetryKeyString] = Object.assign({}, endpoint, {
|
||||||
telemetryMetaData: metadata ? metadata.valueMetadatas : []
|
telemetryMetaData: metadata ? metadata.valueMetadatas : []
|
||||||
});
|
});
|
||||||
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
|
||||||
|
// get latest telemetry value (in case subscription is cached and no new data is coming in)
|
||||||
|
this.requestLatestValue(endpoint);
|
||||||
|
|
||||||
|
this.subscriptions[telemetryKeyString] = this.openmct.telemetry.subscribe(
|
||||||
endpoint,
|
endpoint,
|
||||||
this.telemetryReceived.bind(this, endpoint)
|
this.telemetryReceived.bind(this, endpoint)
|
||||||
);
|
);
|
||||||
@ -91,7 +109,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
|
|
||||||
//force re-computation of condition set result as we might be in a state where
|
//force re-computation of condition set result as we might be in a state where
|
||||||
// there is no telemetry datum coming in for a while or at all.
|
// there is no telemetry datum coming in for a while or at all.
|
||||||
let latestTimestamp = getLatestTimestamp(
|
const latestTimestamp = getLatestTimestamp(
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
this.timeSystems,
|
this.timeSystems,
|
||||||
@ -334,57 +352,54 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return currentCondition;
|
return currentCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLADConditionSetOutput(options) {
|
async requestLADConditionSetOutput(options) {
|
||||||
if (!this.conditions.length) {
|
if (!this.conditions.length) {
|
||||||
return Promise.resolve([]);
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.compositionLoad.then(() => {
|
await this.compositionLoad;
|
||||||
let latestTimestamp;
|
|
||||||
let conditionResults = {};
|
|
||||||
let nextLegOptions = { ...options };
|
|
||||||
delete nextLegOptions.onPartialResponse;
|
|
||||||
|
|
||||||
const conditionRequests = this.conditions.map((condition) =>
|
let latestTimestamp;
|
||||||
condition.requestLADConditionResult(nextLegOptions)
|
let conditionResults = {};
|
||||||
|
let nextLegOptions = { ...options };
|
||||||
|
delete nextLegOptions.onPartialResponse;
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
this.conditions.map((condition) => condition.requestLADConditionResult(nextLegOptions))
|
||||||
|
);
|
||||||
|
|
||||||
|
results.forEach((resultObj) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
data: { result }
|
||||||
|
} = resultObj;
|
||||||
|
|
||||||
|
if (this.findConditionById(id)) {
|
||||||
|
conditionResults[id] = Boolean(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
latestTimestamp = getLatestTimestamp(
|
||||||
|
latestTimestamp,
|
||||||
|
data,
|
||||||
|
this.timeSystems,
|
||||||
|
this.openmct.time.timeSystem()
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.all(conditionRequests).then((results) => {
|
|
||||||
results.forEach((resultObj) => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
data: { result }
|
|
||||||
} = resultObj;
|
|
||||||
if (this.findConditionById(id)) {
|
|
||||||
conditionResults[id] = Boolean(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
latestTimestamp = getLatestTimestamp(
|
|
||||||
latestTimestamp,
|
|
||||||
data,
|
|
||||||
this.timeSystems,
|
|
||||||
this.openmct.time.timeSystem()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
|
||||||
const currentOutput = Object.assign(
|
|
||||||
{
|
|
||||||
output: currentCondition.configuration.output,
|
|
||||||
id: this.conditionSetDomainObject.identifier,
|
|
||||||
conditionId: currentCondition.id
|
|
||||||
},
|
|
||||||
latestTimestamp
|
|
||||||
);
|
|
||||||
|
|
||||||
return [currentOutput];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!Object.values(latestTimestamp).some((timeSystem) => timeSystem)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
||||||
|
const currentOutput = {
|
||||||
|
output: currentCondition.configuration.output,
|
||||||
|
id: this.conditionSetDomainObject.identifier,
|
||||||
|
conditionId: currentCondition.id,
|
||||||
|
...latestTimestamp
|
||||||
|
};
|
||||||
|
|
||||||
|
return [currentOutput];
|
||||||
}
|
}
|
||||||
|
|
||||||
isTelemetryUsed(endpoint) {
|
isTelemetryUsed(endpoint) {
|
||||||
@ -409,7 +424,7 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
||||||
let timestamp = {};
|
let timestamp = {};
|
||||||
const currentTimestamp = normalizedDatum[timeSystemKey];
|
const currentTimestamp = normalizedDatum[timeSystemKey];
|
||||||
timestamp[timeSystemKey] = currentTimestamp;
|
timestamp[timeSystemKey] = currentTimestamp;
|
||||||
|
@ -40,12 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
|||||||
return domainObject.type === 'conditionSet';
|
return domainObject.type === 'conditionSet';
|
||||||
}
|
}
|
||||||
|
|
||||||
request(domainObject, options) {
|
async request(domainObject, options) {
|
||||||
let conditionManager = this.getConditionManager(domainObject);
|
let conditionManager = this.getConditionManager(domainObject);
|
||||||
|
let latestOutput = await conditionManager.requestLADConditionSetOutput(options);
|
||||||
return conditionManager.requestLADConditionSetOutput(options).then((latestOutput) => {
|
return latestOutput;
|
||||||
return latestOutput;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(domainObject, callback) {
|
subscribe(domainObject, callback) {
|
||||||
|
@ -66,7 +66,8 @@ describe('the plugin', function () {
|
|||||||
format: 'utc',
|
format: 'utc',
|
||||||
hints: {
|
hints: {
|
||||||
domain: 1
|
domain: 1
|
||||||
}
|
},
|
||||||
|
source: 'utc'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'testSource',
|
key: 'testSource',
|
||||||
@ -720,6 +721,23 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should evaluate as old when telemetry is not received in the allotted time', (done) => {
|
it('should evaluate as old when telemetry is not received in the allotted time', (done) => {
|
||||||
|
openmct.telemetry = jasmine.createSpyObj('telemetry', [
|
||||||
|
'subscribe',
|
||||||
|
'getMetadata',
|
||||||
|
'request',
|
||||||
|
'getValueFormatter',
|
||||||
|
'abortAllRequests'
|
||||||
|
]);
|
||||||
|
openmct.telemetry.getMetadata.and.returnValue({
|
||||||
|
...testTelemetryObject.telemetry,
|
||||||
|
valueMetadatas: []
|
||||||
|
});
|
||||||
|
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||||
|
openmct.telemetry.getValueFormatter.and.returnValue({
|
||||||
|
parse: function (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||||
conditionMgr.telemetryObjects = {
|
conditionMgr.telemetryObjects = {
|
||||||
@ -741,6 +759,20 @@ describe('the plugin', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not evaluate as old when telemetry is received in the allotted time', (done) => {
|
it('should not evaluate as old when telemetry is received in the allotted time', (done) => {
|
||||||
|
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
|
||||||
|
openmct.telemetry.getMetadata.and.returnValue({
|
||||||
|
...testTelemetryObject.telemetry,
|
||||||
|
valueMetadatas: testTelemetryObject.telemetry.values
|
||||||
|
});
|
||||||
|
const testDatum = {
|
||||||
|
'some-key2': '',
|
||||||
|
utc: 1,
|
||||||
|
testSource: '',
|
||||||
|
'some-key': null,
|
||||||
|
id: 'test-object'
|
||||||
|
};
|
||||||
|
openmct.telemetry.request = jasmine.createSpy('request');
|
||||||
|
openmct.telemetry.request.and.returnValue(Promise.resolve([testDatum]));
|
||||||
const date = 1;
|
const date = 1;
|
||||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
|
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
|
||||||
['0.4'];
|
['0.4'];
|
||||||
@ -750,9 +782,7 @@ describe('the plugin', function () {
|
|||||||
'test-object': testTelemetryObject
|
'test-object': testTelemetryObject
|
||||||
};
|
};
|
||||||
conditionMgr.updateConditionTelemetryObjects();
|
conditionMgr.updateConditionTelemetryObjects();
|
||||||
conditionMgr.telemetryReceived(testTelemetryObject, {
|
conditionMgr.telemetryReceived(testTelemetryObject, testDatum);
|
||||||
utc: date
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(mockListener).toHaveBeenCalledWith({
|
expect(mockListener).toHaveBeenCalledWith({
|
||||||
output: 'Default',
|
output: 'Default',
|
||||||
@ -868,6 +898,12 @@ describe('the plugin', function () {
|
|||||||
it('should stop evaluating conditions when a condition evaluates to true', () => {
|
it('should stop evaluating conditions when a condition evaluates to true', () => {
|
||||||
const date = Date.now();
|
const date = Date.now();
|
||||||
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||||
|
|
||||||
|
openmct.telemetry.getMetadata = jasmine.createSpy('getMetadata');
|
||||||
|
openmct.telemetry.getMetadata.and.returnValue({
|
||||||
|
...testTelemetryObject.telemetry,
|
||||||
|
valueMetadatas: []
|
||||||
|
});
|
||||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||||
conditionMgr.telemetryObjects = {
|
conditionMgr.telemetryObjects = {
|
||||||
'test-object': testTelemetryObject
|
'test-object': testTelemetryObject
|
||||||
|
@ -150,16 +150,15 @@ export default class ImportAsJSONAction {
|
|||||||
* @param {string} namespace
|
* @param {string} namespace
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
_generateNewIdentifiers(tree, namespace) {
|
_generateNewIdentifiers(tree, newNamespace) {
|
||||||
// For each domain object in the file, generate new ID, replace in tree
|
// For each domain object in the file, generate new ID, replace in tree
|
||||||
Object.keys(tree.openmct).forEach((domainObjectId) => {
|
Object.keys(tree.openmct).forEach((domainObjectId) => {
|
||||||
const newId = {
|
|
||||||
namespace,
|
|
||||||
key: uuid()
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldId = parseKeyString(domainObjectId);
|
const oldId = parseKeyString(domainObjectId);
|
||||||
|
|
||||||
|
const newId = {
|
||||||
|
namespace: newNamespace,
|
||||||
|
key: uuid()
|
||||||
|
};
|
||||||
tree = this._rewriteId(oldId, newId, tree);
|
tree = this._rewriteId(oldId, newId, tree);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
@ -228,22 +227,32 @@ export default class ImportAsJSONAction {
|
|||||||
_rewriteId(oldId, newId, tree) {
|
_rewriteId(oldId, newId, tree) {
|
||||||
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
||||||
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
||||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
const newTreeString = JSON.stringify(tree).replace(
|
||||||
|
new RegExp(oldIdKeyString, 'g'),
|
||||||
return JSON.parse(tree, (key, value) => {
|
newIdKeyString
|
||||||
|
);
|
||||||
|
const newTree = JSON.parse(newTreeString, (key, value) => {
|
||||||
if (
|
if (
|
||||||
value !== undefined &&
|
value !== undefined &&
|
||||||
value !== null &&
|
value !== null &&
|
||||||
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
||||||
Object.prototype.hasOwnProperty.call(value, 'namespace') &&
|
Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||||
value.key === oldId.key &&
|
|
||||||
value.namespace === oldId.namespace
|
|
||||||
) {
|
) {
|
||||||
return newId;
|
// first check if key is messed up from regex and contains a colon
|
||||||
} else {
|
// if it does, repair it
|
||||||
return value;
|
if (value.key.includes(':')) {
|
||||||
|
const splitKey = value.key.split(':');
|
||||||
|
value.key = splitKey[1];
|
||||||
|
value.namespace = splitKey[0];
|
||||||
|
}
|
||||||
|
// now check if we need to replace the id
|
||||||
|
if (value.key === oldId.key && value.namespace === oldId.namespace) {
|
||||||
|
return newId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return value;
|
||||||
});
|
});
|
||||||
|
return newTree;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
@ -135,11 +135,75 @@ describe('The import JSON action', function () {
|
|||||||
selectFile: {
|
selectFile: {
|
||||||
name: 'imported object',
|
name: 'imported object',
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
body: "{\"openmct\":{\"c28d230d-e909-4a3e-9840-d9ef469dda70\":{\"identifier\":{\"key\":\"c28d230d-e909-4a3e-9840-d9ef469dda70\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[],\"configuration\":{\"series\":[]},\"modified\":1695837546833,\"location\":\"mine\",\"created\":1695837546833,\"persisted\":1695837546833,\"__proto__\":{\"toString\":\"foobar\"}}},\"rootId\":\"c28d230d-e909-4a3e-9840-d9ef469dda70\"}"
|
body: '{"openmct":{"c28d230d-e909-4a3e-9840-d9ef469dda70":{"identifier":{"key":"c28d230d-e909-4a3e-9840-d9ef469dda70","namespace":""},"name":"Unnamed Overlay Plot","type":"telemetry.plot.overlay","composition":[],"configuration":{"series":[]},"modified":1695837546833,"location":"mine","created":1695837546833,"persisted":1695837546833,"__proto__":{"toString":"foobar"}}},"rootId":"c28d230d-e909-4a3e-9840-d9ef469dda70"}'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.resolve(pollutedResponse);
|
return Promise.resolve(pollutedResponse);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it('preserves the integrity of the namespace and key during import', async () => {
|
||||||
|
const incomingObject = {
|
||||||
|
openmct: {
|
||||||
|
'7323f02a-06ac-438d-bd58-6d6e33b8741e': {
|
||||||
|
name: 'Some Folder',
|
||||||
|
type: 'folder',
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
key: '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6',
|
||||||
|
namespace: ''
|
||||||
|
}
|
||||||
|
],
|
||||||
|
modified: 1710843256162,
|
||||||
|
location: 'mine',
|
||||||
|
created: 1710843243471,
|
||||||
|
persisted: 1710843256162,
|
||||||
|
identifier: {
|
||||||
|
namespace: '',
|
||||||
|
key: '7323f02a-06ac-438d-bd58-6d6e33b8741e'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6': {
|
||||||
|
name: 'Some Clock',
|
||||||
|
type: 'clock',
|
||||||
|
configuration: {
|
||||||
|
baseFormat: 'YYYY/MM/DD hh:mm:ss',
|
||||||
|
use24: 'clock12',
|
||||||
|
timezone: 'UTC'
|
||||||
|
},
|
||||||
|
modified: 1710843256152,
|
||||||
|
location: '7323f02a-06ac-438d-bd58-6d6e33b8741e',
|
||||||
|
created: 1710843256152,
|
||||||
|
persisted: 1710843256152,
|
||||||
|
identifier: {
|
||||||
|
namespace: '',
|
||||||
|
key: '9f6c2d21-5ec8-434c-9fe8-31614ae6d7e6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rootId: '7323f02a-06ac-438d-bd58-6d6e33b8741e'
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetDomainObject = {
|
||||||
|
identifier: {
|
||||||
|
namespace: 'starJones',
|
||||||
|
key: '84438cda-a071-48d1-b9bf-d77bd53e59ba'
|
||||||
|
},
|
||||||
|
type: 'folder'
|
||||||
|
};
|
||||||
|
spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model));
|
||||||
|
try {
|
||||||
|
await importFromJSONAction.onSave(targetDomainObject, {
|
||||||
|
selectFile: { body: JSON.stringify(incomingObject) }
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const callArgs of openmct.objects.save.calls.allArgs()) {
|
||||||
|
const savedObject = callArgs[0]; // Assuming the first argument is the object being saved.
|
||||||
|
expect(savedObject.identifier.key.includes(':')).toBeFalse(); // Ensure no colon in the key.
|
||||||
|
expect(savedObject.identifier.namespace).toBe(targetDomainObject.identifier.namespace);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
fail(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -169,6 +169,8 @@ describe('Notebook plugin:', () => {
|
|||||||
|
|
||||||
openmct.editor = {};
|
openmct.editor = {};
|
||||||
openmct.editor.isEditing = () => false;
|
openmct.editor.isEditing = () => false;
|
||||||
|
openmct.editor.on = () => {};
|
||||||
|
openmct.editor.off = () => {};
|
||||||
|
|
||||||
const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
|
const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
|
||||||
notebookViewProvider = applicableViews.find(
|
notebookViewProvider = applicableViews.find(
|
||||||
|
@ -25,7 +25,7 @@ import mount from 'utils/mount';
|
|||||||
import TableConfigurationComponent from './components/TableConfiguration.vue';
|
import TableConfigurationComponent from './components/TableConfiguration.vue';
|
||||||
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
||||||
|
|
||||||
export default function TableConfigurationViewProvider(openmct) {
|
export default function TableConfigurationViewProvider(openmct, options) {
|
||||||
return {
|
return {
|
||||||
key: 'table-configuration',
|
key: 'table-configuration',
|
||||||
name: 'Config',
|
name: 'Config',
|
||||||
@ -45,7 +45,7 @@ export default function TableConfigurationViewProvider(openmct) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
show: function (element) {
|
show: function (element) {
|
||||||
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct, options);
|
||||||
const { destroy } = mount(
|
const { destroy } = mount(
|
||||||
{
|
{
|
||||||
el: element,
|
el: element,
|
||||||
|
@ -32,14 +32,14 @@ import TelemetryTableRow from './TelemetryTableRow.js';
|
|||||||
import TelemetryTableUnitColumn from './TelemetryTableUnitColumn.js';
|
import TelemetryTableUnitColumn from './TelemetryTableUnitColumn.js';
|
||||||
|
|
||||||
export default class TelemetryTable extends EventEmitter {
|
export default class TelemetryTable extends EventEmitter {
|
||||||
constructor(domainObject, openmct) {
|
constructor(domainObject, openmct, options) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
this.tableComposition = undefined;
|
this.tableComposition = undefined;
|
||||||
this.datumCache = [];
|
this.datumCache = [];
|
||||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
this.configuration = new TelemetryTableConfiguration(domainObject, openmct, options);
|
||||||
this.telemetryMode = this.configuration.getTelemetryMode();
|
this.telemetryMode = this.configuration.getTelemetryMode();
|
||||||
this.rowLimit = this.configuration.getRowLimit();
|
this.rowLimit = this.configuration.getRowLimit();
|
||||||
this.paused = false;
|
this.paused = false;
|
||||||
|
@ -24,11 +24,12 @@ import EventEmitter from 'EventEmitter';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default class TelemetryTableConfiguration extends EventEmitter {
|
export default class TelemetryTableConfiguration extends EventEmitter {
|
||||||
constructor(domainObject, openmct) {
|
constructor(domainObject, openmct, options) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
this.defaultOptions = options;
|
||||||
this.columns = {};
|
this.columns = {};
|
||||||
|
|
||||||
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
||||||
@ -48,10 +49,12 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
|||||||
configuration.columnOrder = configuration.columnOrder || [];
|
configuration.columnOrder = configuration.columnOrder || [];
|
||||||
configuration.cellFormat = configuration.cellFormat || {};
|
configuration.cellFormat = configuration.cellFormat || {};
|
||||||
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
|
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
|
||||||
// anything that doesn't have a telemetryMode existed before the change and should stay as it was for consistency
|
// anything that doesn't have a telemetryMode existed before the change and should
|
||||||
configuration.telemetryMode = configuration.telemetryMode ?? 'unlimited';
|
// take the properties of any passed in defaults or the defaults from the plugin
|
||||||
configuration.persistModeChange = configuration.persistModeChange ?? true;
|
configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode;
|
||||||
configuration.rowLimit = configuration.rowLimit ?? 50;
|
configuration.persistModeChange =
|
||||||
|
configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
|
||||||
|
configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit;
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export default function getTelemetryTableType(options = {}) {
|
export default function getTelemetryTableType(options) {
|
||||||
const { telemetryMode = 'performance', persistModeChange = true, rowLimit = 50 } = options;
|
let { telemetryMode, persistModeChange, rowLimit } = options;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: 'Telemetry Table',
|
name: 'Telemetry Table',
|
||||||
|
@ -33,7 +33,7 @@ export default class TelemetryTableView {
|
|||||||
this.component = null;
|
this.component = null;
|
||||||
|
|
||||||
Object.defineProperty(this, 'table', {
|
Object.defineProperty(this, 'table', {
|
||||||
value: new TelemetryTable(domainObject, openmct),
|
value: new TelemetryTable(domainObject, openmct, options),
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: false
|
configurable: false
|
||||||
});
|
});
|
||||||
|
@ -398,15 +398,17 @@ export default {
|
|||||||
totalNumberOfRows: 0,
|
totalNumberOfRows: 0,
|
||||||
rowContext: {},
|
rowContext: {},
|
||||||
telemetryMode: configuration.telemetryMode,
|
telemetryMode: configuration.telemetryMode,
|
||||||
|
rowLimit: configuration.rowLimit,
|
||||||
persistModeChange: configuration.persistModeChange,
|
persistModeChange: configuration.persistModeChange,
|
||||||
afterLoadActions: []
|
afterLoadActions: [],
|
||||||
|
existingConfiguration: configuration
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
dropTargetStyle() {
|
dropTargetStyle() {
|
||||||
return {
|
return {
|
||||||
top: this.$refs.headersTable.offsetTop + 'px',
|
top: this.$refs.headersHolderEl.offsetTop + 'px',
|
||||||
height: this.totalHeight + this.$refs.headersTable.offsetHeight + 'px',
|
height: this.totalHeight + this.$refs.headersHolderEl.offsetHeight + 'px',
|
||||||
left: this.dropOffsetLeft && this.dropOffsetLeft + 'px'
|
left: this.dropOffsetLeft && this.dropOffsetLeft + 'px'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -595,23 +597,35 @@ export default {
|
|||||||
},
|
},
|
||||||
handleConfigurationChanges(changes) {
|
handleConfigurationChanges(changes) {
|
||||||
const { rowLimit, telemetryMode, persistModeChange } = changes;
|
const { rowLimit, telemetryMode, persistModeChange } = changes;
|
||||||
|
const telemetryModeChanged = this.existingConfiguration.telemetryMode !== telemetryMode;
|
||||||
|
let rowLimitChanged = false;
|
||||||
|
|
||||||
this.persistModeChange = persistModeChange;
|
this.persistModeChange = persistModeChange;
|
||||||
|
|
||||||
|
// both rowLimit changes and telemetryMode changes
|
||||||
|
// require a re-request of telemetry
|
||||||
|
|
||||||
if (this.rowLimit !== rowLimit) {
|
if (this.rowLimit !== rowLimit) {
|
||||||
|
rowLimitChanged = true;
|
||||||
this.rowLimit = rowLimit;
|
this.rowLimit = rowLimit;
|
||||||
this.table.updateRowLimit(rowLimit);
|
this.table.updateRowLimit(rowLimit);
|
||||||
|
|
||||||
if (this.telemetryMode !== telemetryMode) {
|
|
||||||
// need to clear and resubscribe, if different, handled below
|
|
||||||
this.table.clearAndResubscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.telemetryMode !== telemetryMode) {
|
// check for telemetry mode change, because you could technically have persist mode changes
|
||||||
|
// set to false, which could create a state where the configuration saved telemetry mode is
|
||||||
|
// different from the currently set telemetry mode
|
||||||
|
if (telemetryModeChanged && this.telemetryMode !== telemetryMode) {
|
||||||
this.telemetryMode = telemetryMode;
|
this.telemetryMode = telemetryMode;
|
||||||
|
|
||||||
|
// this method also re-requests telemetry
|
||||||
this.table.updateTelemetryMode(telemetryMode);
|
this.table.updateTelemetryMode(telemetryMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rowLimitChanged && !telemetryModeChanged) {
|
||||||
|
this.table.clearAndResubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.existingConfiguration = changes;
|
||||||
},
|
},
|
||||||
updateVisibleRows() {
|
updateVisibleRows() {
|
||||||
if (!this.updatingView) {
|
if (!this.updatingView) {
|
||||||
|
@ -25,10 +25,12 @@ import getTelemetryTableType from './TelemetryTableType.js';
|
|||||||
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
|
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
|
||||||
import TelemetryTableViewActions from './ViewActions.js';
|
import TelemetryTableViewActions from './ViewActions.js';
|
||||||
|
|
||||||
export default function plugin(options) {
|
export default function plugin(
|
||||||
|
options = { telemetryMode: 'performance', persistModeChange: true, rowLimit: 50 }
|
||||||
|
) {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
|
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));
|
||||||
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct));
|
openmct.inspectorViews.addProvider(new TableConfigurationViewProvider(openmct, options));
|
||||||
openmct.types.addType('table', getTelemetryTableType(options));
|
openmct.types.addType('table', getTelemetryTableType(options));
|
||||||
openmct.composition.addPolicy((parent, child) => {
|
openmct.composition.addPolicy((parent, child) => {
|
||||||
if (parent.type === 'table') {
|
if (parent.type === 'table') {
|
||||||
|
@ -195,7 +195,10 @@ describe('the plugin', () => {
|
|||||||
utc: false,
|
utc: false,
|
||||||
'some-key': false,
|
'some-key': false,
|
||||||
'some-other-key': false
|
'some-other-key': false
|
||||||
}
|
},
|
||||||
|
persistModeChange: true,
|
||||||
|
rowLimit: 50,
|
||||||
|
telemetryMode: 'performance'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const testTelemetry = [
|
const testTelemetry = [
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<a class="c-icon-button icon-calendar" @click="toggle"></a>
|
<a class="c-icon-button icon-calendar" @click="toggle"></a>
|
||||||
<div v-if="open" class="c-menu c-menu--mobile-modal c-datetime-picker">
|
<div v-if="open" role="dialog" class="c-menu c-menu--mobile-modal c-datetime-picker">
|
||||||
<div class="c-datetime-picker__close-button">
|
<div class="c-datetime-picker__close-button">
|
||||||
<button class="c-click-icon icon-x-in-circle" @click="toggle"></button>
|
<button class="c-click-icon icon-x-in-circle" @click="toggle"></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
/>
|
/>
|
||||||
<date-picker
|
<date-picker
|
||||||
v-if="isUTCBased"
|
v-if="isUTCBased"
|
||||||
class="c-ctrl-wrapper--menus-left"
|
class="c-ctrl-wrapper--menus-right"
|
||||||
:default-date-time="formattedBounds.start"
|
:default-date-time="formattedBounds.start"
|
||||||
:formatter="timeFormatter"
|
:formatter="timeFormatter"
|
||||||
@date-selected="startDateSelected"
|
@date-selected="startDateSelected"
|
||||||
@ -87,7 +87,7 @@
|
|||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
class="c-button icon-x"
|
class="c-button icon-x"
|
||||||
aria-label="Discard time bounds"
|
aria-label="Discard changes and close time popup"
|
||||||
@click.prevent="hide"
|
@click.prevent="hide"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
></button>
|
></button>
|
||||||
<button
|
<button
|
||||||
class="c-button icon-x"
|
class="c-button icon-x"
|
||||||
aria-label="Discard time offsets"
|
aria-label="Discard changes and close time popup"
|
||||||
@click.prevent="hide"
|
@click.prevent="hide"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -454,6 +454,12 @@
|
|||||||
color: $colorTimeRealtimeFg;
|
color: $colorTimeRealtimeFg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.c-ctrl-wrapper--menus-up{ // A bit hacky, but we are rewriting the CSS class here for ITC such that the calendar opens at the bottom to avoid cutoff
|
||||||
|
.c-menu {
|
||||||
|
top: auto;
|
||||||
|
bottom: revert !important;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,6 +711,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[class*='--menus-down'] {
|
||||||
|
.c-menu {
|
||||||
|
top: auto;
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*='--menus-right'] {
|
||||||
|
.c-menu {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&[class*='--menus-left'],
|
&[class*='--menus-left'],
|
||||||
&[class*='menus-to-left'] {
|
&[class*='menus-to-left'] {
|
||||||
.c-menu {
|
.c-menu {
|
||||||
|
@ -77,12 +77,13 @@ export default {
|
|||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const axisHolder = ref(null);
|
const axisHolder = ref(null);
|
||||||
const { size, startObserving } = useResizeObserver();
|
const { size: containerSize, startObserving } = useResizeObserver();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
startObserving(axisHolder.value);
|
startObserving(axisHolder.value);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
containerSize: size
|
axisHolder,
|
||||||
|
containerSize
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@ -95,8 +96,11 @@ export default {
|
|||||||
contentHeight() {
|
contentHeight() {
|
||||||
this.updateNowMarker();
|
this.updateNowMarker();
|
||||||
},
|
},
|
||||||
containerSize() {
|
containerSize: {
|
||||||
this.resize();
|
handler() {
|
||||||
|
this.resize();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@ -104,7 +108,7 @@ export default {
|
|||||||
this.useSVG = true;
|
this.useSVG = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container = select(this.$refs.axisHolder);
|
this.container = select(this.axisHolder);
|
||||||
this.svgElement = this.container.append('svg:svg');
|
this.svgElement = this.container.append('svg:svg');
|
||||||
// draw x axis with labels. CSS is used to position them.
|
// draw x axis with labels. CSS is used to position them.
|
||||||
this.axisElement = this.svgElement
|
this.axisElement = this.svgElement
|
||||||
@ -122,7 +126,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resize() {
|
resize() {
|
||||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
if (this.axisHolder.clientWidth !== this.width) {
|
||||||
this.setDimensions();
|
this.setDimensions();
|
||||||
this.drawAxis(this.bounds, this.timeSystem);
|
this.drawAxis(this.bounds, this.timeSystem);
|
||||||
this.updateNowMarker();
|
this.updateNowMarker();
|
||||||
@ -139,11 +143,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setDimensions() {
|
setDimensions() {
|
||||||
const axisHolder = this.$refs.axisHolder;
|
this.width = this.axisHolder.clientWidth;
|
||||||
this.width = axisHolder.clientWidth;
|
|
||||||
this.offsetWidth = this.width - this.offset;
|
this.offsetWidth = this.width - this.offset;
|
||||||
|
|
||||||
this.height = Math.round(axisHolder.getBoundingClientRect().height);
|
this.height = Math.round(this.axisHolder.getBoundingClientRect().height);
|
||||||
|
|
||||||
if (this.useSVG) {
|
if (this.useSVG) {
|
||||||
this.svgElement.attr('width', this.width);
|
this.svgElement.attr('width', this.width);
|
||||||
|
Loading…
Reference in New Issue
Block a user