mirror of
https://github.com/nasa/openmct.git
synced 2024-12-25 15:51:04 +00:00
Merge branch 'master' into eval-source-maps
This commit is contained in:
commit
cc58dbd5e7
@ -5,18 +5,19 @@ orbs:
|
||||
executors:
|
||||
pw-focal-development:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.39.0-focal
|
||||
- image: mcr.microsoft.com/playwright:v1.42.1-focal
|
||||
environment:
|
||||
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_LOGLEVEL: "debug" # Enable DEBUG level logging for Percy (Issue: https://github.com/nasa/openmct/issues/5742)
|
||||
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_PARALLEL_TOTAL: 2
|
||||
ubuntu:
|
||||
machine:
|
||||
image: ubuntu-2204:current
|
||||
docker_layer_caching: true
|
||||
commands:
|
||||
build_and_install:
|
||||
description: "All steps used to build and install."
|
||||
description: 'All steps used to build and install.'
|
||||
parameters:
|
||||
node-version:
|
||||
type: string
|
||||
@ -26,7 +27,7 @@ commands:
|
||||
node-version: << parameters.node-version >>
|
||||
- node/install-packages
|
||||
generate_and_store_version_and_filesystem_artifacts:
|
||||
description: "Track important packages and files"
|
||||
description: 'Track important packages and files'
|
||||
steps:
|
||||
- run: |
|
||||
[[ $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:
|
||||
path: /tmp/artifacts/
|
||||
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:
|
||||
suite:
|
||||
type: string
|
||||
@ -101,7 +102,7 @@ jobs:
|
||||
node-version: lts/hydrogen
|
||||
- when: #Only install chrome-beta when running the 'full' suite to save $$$
|
||||
condition:
|
||||
equal: ["full", <<parameters.suite>>]
|
||||
equal: ['full', <<parameters.suite>>]
|
||||
steps:
|
||||
- run: npx playwright install chrome-beta
|
||||
- run:
|
||||
@ -158,7 +159,7 @@ jobs:
|
||||
steps:
|
||||
- build_and_install:
|
||||
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: |
|
||||
export $(cat src/plugins/persistence/couch/.env.ci | xargs)
|
||||
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
|
||||
steps:
|
||||
- generate_and_store_version_and_filesystem_artifacts
|
||||
visual-a11y-tests:
|
||||
visual-a11y:
|
||||
parameters:
|
||||
suite:
|
||||
type: string # ci or full
|
||||
executor: pw-focal-development
|
||||
parallelism: 2
|
||||
steps:
|
||||
- build_and_install:
|
||||
node-version: lts/hydrogen
|
||||
node-version: lts/iron
|
||||
- run: npm run test:e2e:visual:<<parameters.suite>>
|
||||
- store_test_results:
|
||||
path: test-results/results.xml
|
||||
@ -254,8 +256,8 @@ workflows:
|
||||
name: e2e-stable
|
||||
suite: stable
|
||||
- e2e-mobile
|
||||
- visual-a11y-tests:
|
||||
name: visual-a11y-test-ci
|
||||
- visual-a11y:
|
||||
name: visual-a11y-ci
|
||||
suite: ci
|
||||
|
||||
the-nightly: #These jobs do not run on PRs, but against master at night
|
||||
@ -274,13 +276,13 @@ workflows:
|
||||
- e2e-mobile
|
||||
- perf-test
|
||||
- mem-test
|
||||
- visual-a11y-tests:
|
||||
name: visual-a11y-test-nightly
|
||||
- visual-a11y:
|
||||
name: visual-a11y-nightly
|
||||
suite: full
|
||||
- e2e-couchdb
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
cron: '0 0 * * *'
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
2
.github/workflows/e2e-couchdb.yml
vendored
2
.github/workflows/e2e-couchdb.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
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
|
||||
run: |
|
||||
|
2
.github/workflows/e2e-flakefinder.yml
vendored
2
.github/workflows/e2e-flakefinder.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.39.0 install
|
||||
- run: npx playwright@1.42.1 install
|
||||
- run: npm ci --no-audit --progress=false
|
||||
|
||||
- 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: |
|
||||
${{ 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 run test:perf:localhost
|
||||
- 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: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- run: npx playwright@1.39.0 install
|
||||
- run: npx playwright@1.42.1 install
|
||||
- run: npx playwright install chrome-beta
|
||||
- run: npm ci --no-audit --progress=false
|
||||
- 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.
|
||||
|
||||
The project is also monitored by [LGTM](https://lgtm.com/projects/g/nasa/openmct/) and is available to public.
|
||||
|
||||
### ESLint
|
||||
|
||||
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.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: /Plot/ })).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 ({
|
||||
page
|
||||
@ -457,4 +467,11 @@ test.describe('Basic Condition Set Use', () => {
|
||||
|
||||
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',
|
||||
json: examplePlanSmall2
|
||||
});
|
||||
|
||||
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
|
||||
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 }) => {
|
||||
const plan = await createPlanFromJSON(page, {
|
||||
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",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.39.0",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@types/d3-axis": "3.0.6",
|
||||
"@types/d3-scale": "4.0.8",
|
||||
"@types/d3-selection": "3.0.10",
|
||||
"@types/d3-shape": "3.0.0",
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "5.1.2",
|
||||
"@types/lodash": "4.14.192",
|
||||
"@types/lodash": "4.17.0",
|
||||
"@vue/compiler-sfc": "3.4.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
@ -1548,12 +1548,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz",
|
||||
"integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==",
|
||||
"version": "1.42.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz",
|
||||
"integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.39.0"
|
||||
"playwright": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@ -1821,9 +1821,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.14.192",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.192.tgz",
|
||||
"integrity": "sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A==",
|
||||
"version": "4.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
||||
"integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
@ -6027,9 +6027,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -9026,12 +9026,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.39.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz",
|
||||
"integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==",
|
||||
"version": "1.42.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz",
|
||||
"integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.39.0"
|
||||
"playwright-core": "1.42.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@ -9048,7 +9048,6 @@
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz",
|
||||
"integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
@ -9070,18 +9069,6 @@
|
||||
"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": {
|
||||
"version": "2.29.1",
|
||||
"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",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.39.0",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@types/d3-axis": "3.0.6",
|
||||
"@types/d3-shape": "3.0.0",
|
||||
"@types/d3-scale": "4.0.8",
|
||||
"@types/d3-selection": "3.0.10",
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "5.1.2",
|
||||
"@types/lodash": "4.14.192",
|
||||
"@types/lodash": "4.17.0",
|
||||
"@vue/compiler-sfc": "3.4.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
@ -156,4 +156,4 @@
|
||||
"keywords": [
|
||||
"nasa"
|
||||
]
|
||||
}
|
||||
}
|
@ -56,20 +56,38 @@ export default class ConditionManager extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
subscribeToTelemetry(endpoint) {
|
||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||
if (this.subscriptions[id]) {
|
||||
console.log('subscription already exists');
|
||||
async requestLatestValue(endpoint) {
|
||||
const options = {
|
||||
size: 1,
|
||||
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;
|
||||
}
|
||||
|
||||
const metadata = this.openmct.telemetry.getMetadata(endpoint);
|
||||
|
||||
this.telemetryObjects[id] = Object.assign({}, endpoint, {
|
||||
this.telemetryObjects[telemetryKeyString] = Object.assign({}, endpoint, {
|
||||
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,
|
||||
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
|
||||
// there is no telemetry datum coming in for a while or at all.
|
||||
let latestTimestamp = getLatestTimestamp(
|
||||
const latestTimestamp = getLatestTimestamp(
|
||||
{},
|
||||
{},
|
||||
this.timeSystems,
|
||||
@ -334,57 +352,54 @@ export default class ConditionManager extends EventEmitter {
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
requestLADConditionSetOutput(options) {
|
||||
async requestLADConditionSetOutput(options) {
|
||||
if (!this.conditions.length) {
|
||||
return Promise.resolve([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.compositionLoad.then(() => {
|
||||
let latestTimestamp;
|
||||
let conditionResults = {};
|
||||
let nextLegOptions = { ...options };
|
||||
delete nextLegOptions.onPartialResponse;
|
||||
await this.compositionLoad;
|
||||
|
||||
const conditionRequests = this.conditions.map((condition) =>
|
||||
condition.requestLADConditionResult(nextLegOptions)
|
||||
let latestTimestamp;
|
||||
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) {
|
||||
@ -409,7 +424,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||
const timeSystemKey = this.openmct.time.getTimeSystem().key;
|
||||
let timestamp = {};
|
||||
const currentTimestamp = normalizedDatum[timeSystemKey];
|
||||
timestamp[timeSystemKey] = currentTimestamp;
|
||||
|
@ -40,12 +40,10 @@ export default class ConditionSetTelemetryProvider {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
request(domainObject, options) {
|
||||
async request(domainObject, options) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
return conditionManager.requestLADConditionSetOutput(options).then((latestOutput) => {
|
||||
return latestOutput;
|
||||
});
|
||||
let latestOutput = await conditionManager.requestLADConditionSetOutput(options);
|
||||
return latestOutput;
|
||||
}
|
||||
|
||||
subscribe(domainObject, callback) {
|
||||
|
@ -66,7 +66,8 @@ describe('the plugin', function () {
|
||||
format: 'utc',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
source: 'utc'
|
||||
},
|
||||
{
|
||||
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) => {
|
||||
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);
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
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) => {
|
||||
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;
|
||||
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input =
|
||||
['0.4'];
|
||||
@ -750,9 +782,7 @@ describe('the plugin', function () {
|
||||
'test-object': testTelemetryObject
|
||||
};
|
||||
conditionMgr.updateConditionTelemetryObjects();
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, {
|
||||
utc: date
|
||||
});
|
||||
conditionMgr.telemetryReceived(testTelemetryObject, testDatum);
|
||||
setTimeout(() => {
|
||||
expect(mockListener).toHaveBeenCalledWith({
|
||||
output: 'Default',
|
||||
@ -868,6 +898,12 @@ describe('the plugin', function () {
|
||||
it('should stop evaluating conditions when a condition evaluates to true', () => {
|
||||
const date = Date.now();
|
||||
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.telemetryObjects = {
|
||||
'test-object': testTelemetryObject
|
||||
|
@ -150,16 +150,15 @@ export default class ImportAsJSONAction {
|
||||
* @param {string} namespace
|
||||
* @returns {object}
|
||||
*/
|
||||
_generateNewIdentifiers(tree, namespace) {
|
||||
_generateNewIdentifiers(tree, newNamespace) {
|
||||
// For each domain object in the file, generate new ID, replace in tree
|
||||
Object.keys(tree.openmct).forEach((domainObjectId) => {
|
||||
const newId = {
|
||||
namespace,
|
||||
key: uuid()
|
||||
};
|
||||
|
||||
const oldId = parseKeyString(domainObjectId);
|
||||
|
||||
const newId = {
|
||||
namespace: newNamespace,
|
||||
key: uuid()
|
||||
};
|
||||
tree = this._rewriteId(oldId, newId, tree);
|
||||
}, this);
|
||||
|
||||
@ -228,22 +227,32 @@ export default class ImportAsJSONAction {
|
||||
_rewriteId(oldId, newId, tree) {
|
||||
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
||||
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
||||
tree = JSON.stringify(tree).replace(new RegExp(oldIdKeyString, 'g'), newIdKeyString);
|
||||
|
||||
return JSON.parse(tree, (key, value) => {
|
||||
const newTreeString = JSON.stringify(tree).replace(
|
||||
new RegExp(oldIdKeyString, 'g'),
|
||||
newIdKeyString
|
||||
);
|
||||
const newTree = JSON.parse(newTreeString, (key, value) => {
|
||||
if (
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'namespace') &&
|
||||
value.key === oldId.key &&
|
||||
value.namespace === oldId.namespace
|
||||
Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||
) {
|
||||
return newId;
|
||||
} else {
|
||||
return value;
|
||||
// first check if key is messed up from regex and contains a colon
|
||||
// if it does, repair it
|
||||
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
|
||||
|
@ -135,11 +135,75 @@ describe('The import JSON action', function () {
|
||||
selectFile: {
|
||||
name: 'imported object',
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
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.isEditing = () => false;
|
||||
openmct.editor.on = () => {};
|
||||
openmct.editor.off = () => {};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
|
||||
notebookViewProvider = applicableViews.find(
|
||||
|
@ -25,7 +25,7 @@ import mount from 'utils/mount';
|
||||
import TableConfigurationComponent from './components/TableConfiguration.vue';
|
||||
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
|
||||
|
||||
export default function TableConfigurationViewProvider(openmct) {
|
||||
export default function TableConfigurationViewProvider(openmct, options) {
|
||||
return {
|
||||
key: 'table-configuration',
|
||||
name: 'Config',
|
||||
@ -45,7 +45,7 @@ export default function TableConfigurationViewProvider(openmct) {
|
||||
|
||||
return {
|
||||
show: function (element) {
|
||||
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
tableConfiguration = new TelemetryTableConfiguration(domainObject, openmct, options);
|
||||
const { destroy } = mount(
|
||||
{
|
||||
el: element,
|
||||
|
@ -32,14 +32,14 @@ import TelemetryTableRow from './TelemetryTableRow.js';
|
||||
import TelemetryTableUnitColumn from './TelemetryTableUnitColumn.js';
|
||||
|
||||
export default class TelemetryTable extends EventEmitter {
|
||||
constructor(domainObject, openmct) {
|
||||
constructor(domainObject, openmct, options) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.tableComposition = undefined;
|
||||
this.datumCache = [];
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
|
||||
this.configuration = new TelemetryTableConfiguration(domainObject, openmct, options);
|
||||
this.telemetryMode = this.configuration.getTelemetryMode();
|
||||
this.rowLimit = this.configuration.getRowLimit();
|
||||
this.paused = false;
|
||||
|
@ -24,11 +24,12 @@ import EventEmitter from 'EventEmitter';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
constructor(domainObject, openmct) {
|
||||
constructor(domainObject, openmct, options) {
|
||||
super();
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.openmct = openmct;
|
||||
this.defaultOptions = options;
|
||||
this.columns = {};
|
||||
|
||||
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
|
||||
@ -48,10 +49,12 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
configuration.columnOrder = configuration.columnOrder || [];
|
||||
configuration.cellFormat = configuration.cellFormat || {};
|
||||
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
|
||||
configuration.telemetryMode = configuration.telemetryMode ?? 'unlimited';
|
||||
configuration.persistModeChange = configuration.persistModeChange ?? true;
|
||||
configuration.rowLimit = configuration.rowLimit ?? 50;
|
||||
// anything that doesn't have a telemetryMode existed before the change and should
|
||||
// take the properties of any passed in defaults or the defaults from the plugin
|
||||
configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode;
|
||||
configuration.persistModeChange =
|
||||
configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
|
||||
configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
@ -20,8 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
export default function getTelemetryTableType(options = {}) {
|
||||
const { telemetryMode = 'performance', persistModeChange = true, rowLimit = 50 } = options;
|
||||
export default function getTelemetryTableType(options) {
|
||||
let { telemetryMode, persistModeChange, rowLimit } = options;
|
||||
|
||||
return {
|
||||
name: 'Telemetry Table',
|
||||
|
@ -33,7 +33,7 @@ export default class TelemetryTableView {
|
||||
this.component = null;
|
||||
|
||||
Object.defineProperty(this, 'table', {
|
||||
value: new TelemetryTable(domainObject, openmct),
|
||||
value: new TelemetryTable(domainObject, openmct, options),
|
||||
enumerable: false,
|
||||
configurable: false
|
||||
});
|
||||
|
@ -398,15 +398,17 @@ export default {
|
||||
totalNumberOfRows: 0,
|
||||
rowContext: {},
|
||||
telemetryMode: configuration.telemetryMode,
|
||||
rowLimit: configuration.rowLimit,
|
||||
persistModeChange: configuration.persistModeChange,
|
||||
afterLoadActions: []
|
||||
afterLoadActions: [],
|
||||
existingConfiguration: configuration
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dropTargetStyle() {
|
||||
return {
|
||||
top: this.$refs.headersTable.offsetTop + 'px',
|
||||
height: this.totalHeight + this.$refs.headersTable.offsetHeight + 'px',
|
||||
top: this.$refs.headersHolderEl.offsetTop + 'px',
|
||||
height: this.totalHeight + this.$refs.headersHolderEl.offsetHeight + 'px',
|
||||
left: this.dropOffsetLeft && this.dropOffsetLeft + 'px'
|
||||
};
|
||||
},
|
||||
@ -595,23 +597,35 @@ export default {
|
||||
},
|
||||
handleConfigurationChanges(changes) {
|
||||
const { rowLimit, telemetryMode, persistModeChange } = changes;
|
||||
const telemetryModeChanged = this.existingConfiguration.telemetryMode !== telemetryMode;
|
||||
let rowLimitChanged = false;
|
||||
|
||||
this.persistModeChange = persistModeChange;
|
||||
|
||||
// both rowLimit changes and telemetryMode changes
|
||||
// require a re-request of telemetry
|
||||
|
||||
if (this.rowLimit !== rowLimit) {
|
||||
rowLimitChanged = true;
|
||||
this.rowLimit = 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 method also re-requests telemetry
|
||||
this.table.updateTelemetryMode(telemetryMode);
|
||||
}
|
||||
|
||||
if (rowLimitChanged && !telemetryModeChanged) {
|
||||
this.table.clearAndResubscribe();
|
||||
}
|
||||
|
||||
this.existingConfiguration = changes;
|
||||
},
|
||||
updateVisibleRows() {
|
||||
if (!this.updatingView) {
|
||||
|
@ -25,10 +25,12 @@ import getTelemetryTableType from './TelemetryTableType.js';
|
||||
import TelemetryTableViewProvider from './TelemetryTableViewProvider.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) {
|
||||
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.composition.addPolicy((parent, child) => {
|
||||
if (parent.type === 'table') {
|
||||
|
@ -195,7 +195,10 @@ describe('the plugin', () => {
|
||||
utc: false,
|
||||
'some-key': false,
|
||||
'some-other-key': false
|
||||
}
|
||||
},
|
||||
persistModeChange: true,
|
||||
rowLimit: 50,
|
||||
telemetryMode: 'performance'
|
||||
}
|
||||
};
|
||||
const testTelemetry = [
|
||||
|
@ -29,7 +29,7 @@
|
||||
}"
|
||||
>
|
||||
<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">
|
||||
<button class="c-click-icon icon-x-in-circle" @click="toggle"></button>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
||||
/>
|
||||
<date-picker
|
||||
v-if="isUTCBased"
|
||||
class="c-ctrl-wrapper--menus-left"
|
||||
class="c-ctrl-wrapper--menus-right"
|
||||
:default-date-time="formattedBounds.start"
|
||||
:formatter="timeFormatter"
|
||||
@date-selected="startDateSelected"
|
||||
@ -87,7 +87,7 @@
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
aria-label="Discard time bounds"
|
||||
aria-label="Discard changes and close time popup"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
|
@ -132,7 +132,7 @@
|
||||
></button>
|
||||
<button
|
||||
class="c-button icon-x"
|
||||
aria-label="Discard time offsets"
|
||||
aria-label="Discard changes and close time popup"
|
||||
@click.prevent="hide"
|
||||
></button>
|
||||
</div>
|
||||
|
@ -454,6 +454,12 @@
|
||||
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-to-left'] {
|
||||
.c-menu {
|
||||
|
@ -77,12 +77,13 @@ export default {
|
||||
},
|
||||
setup() {
|
||||
const axisHolder = ref(null);
|
||||
const { size, startObserving } = useResizeObserver();
|
||||
const { size: containerSize, startObserving } = useResizeObserver();
|
||||
onMounted(() => {
|
||||
startObserving(axisHolder.value);
|
||||
});
|
||||
return {
|
||||
containerSize: size
|
||||
axisHolder,
|
||||
containerSize
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@ -95,8 +96,11 @@ export default {
|
||||
contentHeight() {
|
||||
this.updateNowMarker();
|
||||
},
|
||||
containerSize() {
|
||||
this.resize();
|
||||
containerSize: {
|
||||
handler() {
|
||||
this.resize();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@ -104,7 +108,7 @@ export default {
|
||||
this.useSVG = true;
|
||||
}
|
||||
|
||||
this.container = select(this.$refs.axisHolder);
|
||||
this.container = select(this.axisHolder);
|
||||
this.svgElement = this.container.append('svg:svg');
|
||||
// draw x axis with labels. CSS is used to position them.
|
||||
this.axisElement = this.svgElement
|
||||
@ -122,7 +126,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
resize() {
|
||||
if (this.$refs.axisHolder.clientWidth !== this.width) {
|
||||
if (this.axisHolder.clientWidth !== this.width) {
|
||||
this.setDimensions();
|
||||
this.drawAxis(this.bounds, this.timeSystem);
|
||||
this.updateNowMarker();
|
||||
@ -139,11 +143,10 @@ export default {
|
||||
}
|
||||
},
|
||||
setDimensions() {
|
||||
const axisHolder = this.$refs.axisHolder;
|
||||
this.width = axisHolder.clientWidth;
|
||||
this.width = this.axisHolder.clientWidth;
|
||||
this.offsetWidth = this.width - this.offset;
|
||||
|
||||
this.height = Math.round(axisHolder.getBoundingClientRect().height);
|
||||
this.height = Math.round(this.axisHolder.getBoundingClientRect().height);
|
||||
|
||||
if (this.useSVG) {
|
||||
this.svgElement.attr('width', this.width);
|
||||
|
Loading…
Reference in New Issue
Block a user