Compare commits
28 Commits
fix-SP
...
persistenc
Author | SHA1 | Date | |
---|---|---|---|
5de5ff347c | |||
2bfe632e7e | |||
4ac39a3990 | |||
169d148c58 | |||
40d2f3295f | |||
0e707150e0 | |||
2540d96617 | |||
1c8784fec5 | |||
2943d2b6ec | |||
4246a597a9 | |||
0af7965021 | |||
e9c0909415 | |||
0f0a3dc48f | |||
4c82680b87 | |||
c4734b8ad6 | |||
9786ff5de4 | |||
437154a5c0 | |||
2bd38dab9f | |||
063df721ae | |||
a09db30b32 | |||
9d89bdd6d3 | |||
ed9ca2829b | |||
eacbac6aad | |||
69153fe8f0 | |||
51196530fd | |||
fefa46ce7e | |||
e08ab8ef24 | |||
7011877e64 |
3
.github/workflows/e2e-pr.yml
vendored
@ -30,7 +30,8 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
- run: npx playwright@1.21.1 install
|
- run: npx playwright@1.23.0 install
|
||||||
|
- run: npx playwright install chrome-beta
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run test:e2e:full
|
- run: npm run test:e2e:full
|
||||||
- name: Archive test results
|
- name: Archive test results
|
||||||
|
2
.github/workflows/e2e-visual.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
- run: npx playwright@1.21.1 install
|
- run: npx playwright@1.23.0 install
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- name: Run the e2e visual tests
|
- name: Run the e2e visual tests
|
||||||
run: npm run test:e2e:visual
|
run: npm run test:e2e:visual
|
||||||
|
31
app.js
@ -12,6 +12,7 @@ const express = require('express');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const request = require('request');
|
const request = require('request');
|
||||||
|
const __DEV__ = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
options.port = options.port || options.p || 8080;
|
options.port = options.port || options.p || 8080;
|
||||||
@ -49,14 +50,18 @@ class WatchRunPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const webpackConfig = process.env.CI ? require('./webpack.coverage.js') : require('./webpack.dev.js');
|
let webpackConfig;
|
||||||
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
if (__DEV__) {
|
||||||
webpackConfig.plugins.push(new WatchRunPlugin());
|
webpackConfig = require('./webpack.dev');
|
||||||
|
webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||||
webpackConfig.entry.openmct = [
|
webpackConfig.entry.openmct = [
|
||||||
'webpack-hot-middleware/client?reload=true',
|
'webpack-hot-middleware/client?reload=true',
|
||||||
webpackConfig.entry.openmct
|
webpackConfig.entry.openmct
|
||||||
];
|
];
|
||||||
|
webpackConfig.plugins.push(new WatchRunPlugin());
|
||||||
|
} else {
|
||||||
|
webpackConfig = require('./webpack.coverage');
|
||||||
|
}
|
||||||
|
|
||||||
const compiler = webpack(webpackConfig);
|
const compiler = webpack(webpackConfig);
|
||||||
|
|
||||||
@ -68,10 +73,12 @@ app.use(require('webpack-dev-middleware')(
|
|||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
app.use(require('webpack-hot-middleware')(
|
if (__DEV__) {
|
||||||
compiler,
|
app.use(require('webpack-hot-middleware')(
|
||||||
{}
|
compiler,
|
||||||
));
|
{}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Expose index.html for development users.
|
// Expose index.html for development users.
|
||||||
app.get('/', function (req, res) {
|
app.get('/', function (req, res) {
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const { devices } = require('@playwright/test');
|
const { devices } = require('@playwright/test');
|
||||||
|
const MAX_FAILURES = 5;
|
||||||
|
const NUM_WORKERS = 2;
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
@ -12,20 +14,20 @@ const config = {
|
|||||||
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
testIgnore: '**/*.perf.spec.js', //Ignore performance tests and define in playwright-perfromance.config.js
|
||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'cross-env NODE_ENV=test npm run start',
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: false
|
||||||
},
|
},
|
||||||
maxFailures: process.env.CI ? 5 : undefined, //Limits failures to 5 to reduce CI Waste
|
maxFailures: MAX_FAILURES, //Limits failures to 5 to reduce CI Waste
|
||||||
workers: 2, //Limit to 2 for CircleCI Agent
|
workers: NUM_WORKERS, //Limit to 2 for CircleCI Agent
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: true,
|
headless: true,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
video: 'on-first-retry'
|
video: 'off'
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
|
@ -12,10 +12,10 @@ const config = {
|
|||||||
testIgnore: '**/*.perf.spec.js',
|
testIgnore: '**/*.perf.spec.js',
|
||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'cross-env NODE_ENV=test npm run start',
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 120 * 1000,
|
timeout: 120 * 1000,
|
||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: true
|
||||||
},
|
},
|
||||||
workers: 1,
|
workers: 1,
|
||||||
use: {
|
use: {
|
||||||
@ -25,7 +25,7 @@ const config = {
|
|||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'only-on-failure',
|
screenshot: 'only-on-failure',
|
||||||
trace: 'retain-on-failure',
|
trace: 'retain-on-failure',
|
||||||
video: 'retain-on-failure'
|
video: 'off'
|
||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// playwright.config.js
|
// playwright.config.js
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
const CI = process.env.CI === 'true';
|
||||||
|
|
||||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
retries: 1, //Only for debugging purposes because trace is enabled only on first retry
|
retries: 1, //Only for debugging purposes because trace is enabled only on first retry
|
||||||
@ -9,15 +11,15 @@ const config = {
|
|||||||
timeout: 60 * 1000,
|
timeout: 60 * 1000,
|
||||||
workers: 1, //Only run in serial with 1 worker
|
workers: 1, //Only run in serial with 1 worker
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'cross-env NODE_ENV=test npm run start',
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: !CI
|
||||||
},
|
},
|
||||||
use: {
|
use: {
|
||||||
browserName: "chromium",
|
browserName: "chromium",
|
||||||
baseURL: 'http://localhost:8080/',
|
baseURL: 'http://localhost:8080/',
|
||||||
headless: Boolean(process.env.CI), //Only if running locally
|
headless: CI, //Only if running locally
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'off',
|
screenshot: 'off',
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
|
@ -9,7 +9,7 @@ const config = {
|
|||||||
timeout: 90 * 1000,
|
timeout: 90 * 1000,
|
||||||
workers: 1, // visual tests should never run in parallel due to test pollution
|
workers: 1, // visual tests should never run in parallel due to test pollution
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npm run start',
|
command: 'cross-env NODE_ENV=test npm run start',
|
||||||
url: 'http://localhost:8080/#',
|
url: 'http://localhost:8080/#',
|
||||||
timeout: 200 * 1000,
|
timeout: 200 * 1000,
|
||||||
reuseExistingServer: !process.env.CI
|
reuseExistingServer: !process.env.CI
|
||||||
@ -21,7 +21,7 @@ const config = {
|
|||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
screenshot: 'on',
|
screenshot: 'on',
|
||||||
trace: 'off',
|
trace: 'off',
|
||||||
video: 'on'
|
video: 'off'
|
||||||
},
|
},
|
||||||
reporter: [
|
reporter: [
|
||||||
['list'],
|
['list'],
|
||||||
|
@ -36,7 +36,7 @@ test.describe('Branding tests', () => {
|
|||||||
await page.click('.l-shell__app-logo');
|
await page.click('.l-shell__app-logo');
|
||||||
|
|
||||||
// Verify that the NASA Logo Appears
|
// Verify that the NASA Logo Appears
|
||||||
await expect(await page.locator('.c-about__image')).toBeVisible();
|
await expect(page.locator('.c-about__image')).toBeVisible();
|
||||||
|
|
||||||
// Modify the Build information in 'about' Modal
|
// Modify the Build information in 'about' Modal
|
||||||
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
|
const versionInformationLocator = page.locator('ul.t-info.l-info.s-info');
|
||||||
|
55
e2e/tests/framework.e2e.spec.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test suite is dedicated to testing our use of the playwright framework as it
|
||||||
|
relates to how we've extended it (i.e. ./e2e/fixtures.js) and assumptions made in our dev environment
|
||||||
|
(app.js and ./e2e/webpack-dev-middleware.js)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { test } = require('../fixtures.js');
|
||||||
|
|
||||||
|
test.describe('fixtures.js tests', () => {
|
||||||
|
test('Verify that tests fail if console.error is thrown', async ({ page }) => {
|
||||||
|
test.fail();
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Verify that ../fixtures.js detects console log errors
|
||||||
|
await Promise.all([
|
||||||
|
page.evaluate(() => console.error('This should result in a failure')),
|
||||||
|
page.waitForEvent('console') // always wait for the event to happen while triggering it!
|
||||||
|
]);
|
||||||
|
|
||||||
|
});
|
||||||
|
test('Verify that tests pass if console.warn is thrown', async ({ page }) => {
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Verify that ../fixtures.js detects console log errors
|
||||||
|
await Promise.all([
|
||||||
|
page.evaluate(() => console.warn('This should result in a pass')),
|
||||||
|
page.waitForEvent('console') // always wait for the event to happen while triggering it!
|
||||||
|
]);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -55,16 +55,13 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' });
|
await context.storageState({ path: './e2e/test-data/recycled_local_storage.json' });
|
||||||
|
|
||||||
//Set object identifier from url
|
//Set object identifier from url
|
||||||
conditionSetUrl = await page.url();
|
conditionSetUrl = page.url();
|
||||||
console.log('conditionSetUrl ' + conditionSetUrl);
|
console.log('conditionSetUrl ' + conditionSetUrl);
|
||||||
|
|
||||||
getConditionSetIdentifierFromUrl = await conditionSetUrl.split('/').pop().split('?')[0];
|
getConditionSetIdentifierFromUrl = conditionSetUrl.split('/').pop().split('?')[0];
|
||||||
console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
|
console.debug('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl);
|
||||||
await page.close();
|
|
||||||
});
|
|
||||||
test.afterAll(async ({ browser }) => {
|
|
||||||
await browser.close();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Load localStorage for subsequent tests
|
//Load localStorage for subsequent tests
|
||||||
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
|
test.use({ storageState: './e2e/test-data/recycled_local_storage.json' });
|
||||||
//Begin suite of tests again localStorage
|
//Begin suite of tests again localStorage
|
||||||
@ -76,7 +73,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
|
|
||||||
//Assertions on loaded Condition Set in Inspector
|
//Assertions on loaded Condition Set in Inspector
|
||||||
await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy;
|
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
|
||||||
|
|
||||||
//Reload Page
|
//Reload Page
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -87,7 +84,7 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
//Re-verify after reload
|
//Re-verify after reload
|
||||||
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set');
|
||||||
//Assertions on loaded Condition Set in Inspector
|
//Assertions on loaded Condition Set in Inspector
|
||||||
await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy;
|
expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy();
|
||||||
|
|
||||||
});
|
});
|
||||||
test('condition set object can be modified on @localStorage', async ({ page }) => {
|
test('condition set object can be modified on @localStorage', async ({ page }) => {
|
||||||
@ -113,18 +110,18 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
|
|
||||||
// Verify Inspector properties
|
// Verify Inspector properties
|
||||||
// Verify Inspector has updated Name property
|
// Verify Inspector has updated Name property
|
||||||
await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
|
expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
|
||||||
// Verify Inspector Details has updated Name property
|
// Verify Inspector Details has updated Name property
|
||||||
await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
|
expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
|
||||||
|
|
||||||
// Verify Tree reflects updated Name proprety
|
// Verify Tree reflects updated Name proprety
|
||||||
// Expand Tree
|
// Expand Tree
|
||||||
await page.locator('text=Open MCT My Items >> span >> nth=3').click();
|
await page.locator('text=Open MCT My Items >> span >> nth=3').click();
|
||||||
// Verify Condition Set Object is renamed in Tree
|
// Verify Condition Set Object is renamed in Tree
|
||||||
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
// Verify Search Tree reflects renamed Name property
|
// Verify Search Tree reflects renamed Name property
|
||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
|
||||||
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
|
|
||||||
//Reload Page
|
//Reload Page
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@ -137,18 +134,18 @@ test.describe.serial('Condition Set CRUD Operations on @localStorage', () => {
|
|||||||
|
|
||||||
// Verify Inspector properties
|
// Verify Inspector properties
|
||||||
// Verify Inspector has updated Name property
|
// Verify Inspector has updated Name property
|
||||||
await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
|
expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy();
|
||||||
// Verify Inspector Details has updated Name property
|
// Verify Inspector Details has updated Name property
|
||||||
await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
|
expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy();
|
||||||
|
|
||||||
// Verify Tree reflects updated Name proprety
|
// Verify Tree reflects updated Name proprety
|
||||||
// Expand Tree
|
// Expand Tree
|
||||||
await page.locator('text=Open MCT My Items >> span >> nth=3').click();
|
await page.locator('text=Open MCT My Items >> span >> nth=3').click();
|
||||||
// Verify Condition Set Object is renamed in Tree
|
// Verify Condition Set Object is renamed in Tree
|
||||||
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
// Verify Search Tree reflects renamed Name property
|
// Verify Search Tree reflects renamed Name property
|
||||||
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
|
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('Renamed');
|
||||||
await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy();
|
||||||
});
|
});
|
||||||
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => {
|
test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => {
|
||||||
//Navigate to baseURL
|
//Navigate to baseURL
|
||||||
|
@ -172,7 +172,8 @@ test.describe('Example Imagery Object', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can use the reset button to reset the image', async ({ page }) => {
|
test('Can use the reset button to reset the image', async ({ page }, testInfo) => {
|
||||||
|
test.slow(testInfo.project === 'chrome-beta', "This test is slow in chrome-beta");
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await page.locator(backgroundImageSelector).hover({trial: true});
|
await page.locator(backgroundImageSelector).hover({trial: true});
|
||||||
|
|
||||||
@ -191,16 +192,17 @@ test.describe('Example Imagery Object', () => {
|
|||||||
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
expect.soft(zoomedInBoundingBox.height).toBeGreaterThan(initialBoundingBox.height);
|
||||||
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
|
expect.soft(zoomedInBoundingBox.width).toBeGreaterThan(initialBoundingBox.width);
|
||||||
|
|
||||||
await zoomResetBtn.click();
|
|
||||||
// wait for zoom animation to finish
|
// wait for zoom animation to finish
|
||||||
await page.locator(backgroundImageSelector).hover({trial: true});
|
// FIXME: The zoom is flakey, sometimes not returning to original dimensions
|
||||||
|
// https://github.com/nasa/openmct/issues/5491
|
||||||
|
await expect.poll(async () => {
|
||||||
|
await zoomResetBtn.click();
|
||||||
|
const boundingBox = await page.locator(backgroundImageSelector).boundingBox();
|
||||||
|
|
||||||
const resetBoundingBox = await page.locator(backgroundImageSelector).boundingBox();
|
return boundingBox;
|
||||||
expect.soft(resetBoundingBox.height).toBeLessThan(zoomedInBoundingBox.height);
|
}, {
|
||||||
expect.soft(resetBoundingBox.width).toBeLessThan(zoomedInBoundingBox.width);
|
timeout: 10 * 1000
|
||||||
|
}).toEqual(initialBoundingBox);
|
||||||
expect.soft(resetBoundingBox.height).toEqual(initialBoundingBox.height);
|
|
||||||
expect(resetBoundingBox.width).toEqual(initialBoundingBox.width);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Using the zoom features does not pause telemetry', async ({ page }) => {
|
test('Using the zoom features does not pause telemetry', async ({ page }) => {
|
||||||
|
@ -35,7 +35,7 @@ test.describe('Restricted Notebook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Can be renamed @addInit', async ({ page }) => {
|
test('Can be renamed @addInit', async ({ page }) => {
|
||||||
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText(`Unnamed ${CUSTOM_NAME}`);
|
await expect(page.locator('.l-browse-bar__object-name')).toContainText(`Unnamed ${CUSTOM_NAME}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can be deleted if there are no locked pages @addInit', async ({ page }) => {
|
test('Can be deleted if there are no locked pages @addInit', async ({ page }) => {
|
||||||
@ -52,16 +52,15 @@ test.describe('Restricted Notebook', () => {
|
|||||||
// Click Remove Text
|
// Click Remove Text
|
||||||
await page.locator('text=Remove').click();
|
await page.locator('text=Remove').click();
|
||||||
|
|
||||||
//Wait until Save Banner is gone
|
// Click 'OK' on confirmation window and wait for save banner to appear
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=OK').click(),
|
page.locator('text=OK').click(),
|
||||||
page.waitForSelector('.c-message-banner__message')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
|
|
||||||
// has been deleted
|
// has been deleted
|
||||||
expect.soft(await restrictedNotebookTreeObject.count()).toEqual(0);
|
expect(await restrictedNotebookTreeObject.count()).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can be locked if at least one page has one entry @addInit', async ({ page }) => {
|
test('Can be locked if at least one page has one entry @addInit', async ({ page }) => {
|
||||||
@ -69,7 +68,7 @@ test.describe('Restricted Notebook', () => {
|
|||||||
await enterTextEntry(page);
|
await enterTextEntry(page);
|
||||||
|
|
||||||
const commitButton = page.locator('button:has-text("Commit Entries")');
|
const commitButton = page.locator('button:has-text("Commit Entries")');
|
||||||
expect.soft(await commitButton.count()).toEqual(1);
|
expect(await commitButton.count()).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -81,11 +80,17 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
|||||||
await enterTextEntry(page);
|
await enterTextEntry(page);
|
||||||
await lockPage(page);
|
await lockPage(page);
|
||||||
|
|
||||||
|
// FIXME: Give ample time for the mutation to happen
|
||||||
|
// https://github.com/nasa/openmct/issues/5409
|
||||||
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
||||||
|
await page.waitForTimeout(1 * 1000);
|
||||||
|
|
||||||
// open sidebar
|
// open sidebar
|
||||||
await page.locator('button.c-notebook__toggle-nav-button').click();
|
await page.locator('button.c-notebook__toggle-nav-button').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Locked page should now be in a locked state @addInit', async ({ page }) => {
|
test('Locked page should now be in a locked state @addInit', async ({ page }, testInfo) => {
|
||||||
|
test.fixme(testInfo.project === 'chrome-beta', "Test is unreliable on chrome-beta");
|
||||||
// main lock message on page
|
// main lock message on page
|
||||||
const lockMessage = page.locator('text=This page has been committed and cannot be modified or removed');
|
const lockMessage = page.locator('text=This page has been committed and cannot be modified or removed');
|
||||||
expect.soft(await lockMessage.count()).toEqual(1);
|
expect.soft(await lockMessage.count()).toEqual(1);
|
||||||
@ -96,11 +101,9 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
|||||||
|
|
||||||
// no way to remove a restricted notebook with a locked page
|
// no way to remove a restricted notebook with a locked page
|
||||||
await openContextMenuRestrictedNotebook(page);
|
await openContextMenuRestrictedNotebook(page);
|
||||||
|
|
||||||
const menuOptions = page.locator('.c-menu ul');
|
const menuOptions = page.locator('.c-menu ul');
|
||||||
|
|
||||||
await expect.soft(menuOptions).not.toContainText('Remove');
|
await expect(menuOptions).not.toContainText('Remove');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({ page }) => {
|
test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({ page }) => {
|
||||||
@ -139,7 +142,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
|||||||
|
|
||||||
// deleted page, should no longer exist
|
// deleted page, should no longer exist
|
||||||
const deletedPageElement = page.locator(`text=${TEST_TEXT_NAME}`);
|
const deletedPageElement = page.locator(`text=${TEST_TEXT_NAME}`);
|
||||||
expect.soft(await deletedPageElement.count()).toEqual(0);
|
expect(await deletedPageElement.count()).toEqual(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,7 +158,7 @@ test.describe('Restricted Notebook with a page locked and with an embed @addInit
|
|||||||
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
||||||
|
|
||||||
const embedMenu = page.locator('body >> .c-menu');
|
const embedMenu = page.locator('body >> .c-menu');
|
||||||
await expect.soft(embedMenu).toContainText('Remove This Embed');
|
await expect(embedMenu).toContainText('Remove This Embed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
|
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
|
||||||
@ -164,7 +167,7 @@ test.describe('Restricted Notebook with a page locked and with an embed @addInit
|
|||||||
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
||||||
|
|
||||||
const embedMenu = page.locator('body >> .c-menu');
|
const embedMenu = page.locator('body >> .c-menu');
|
||||||
await expect.soft(embedMenu).not.toContainText('Remove This Embed');
|
await expect(embedMenu).not.toContainText('Remove This Embed');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -232,28 +235,18 @@ async function lockPage(page) {
|
|||||||
await commitButton.click();
|
await commitButton.click();
|
||||||
|
|
||||||
//Wait until Lock Banner is visible
|
//Wait until Lock Banner is visible
|
||||||
await Promise.all([
|
await page.locator('text=Lock Page').click();
|
||||||
page.locator('text=Lock Page').click(),
|
|
||||||
page.waitForSelector('.c-message-banner__message')
|
|
||||||
]);
|
|
||||||
// Close Lock Banner
|
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
|
|
||||||
//artifically wait to avoid mutation delay TODO: https://github.com/nasa/openmct/issues/5409
|
|
||||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
|
||||||
await page.waitForTimeout(1 * 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('@playwright/test').Page} page
|
* @param {import('@playwright/test').Page} page
|
||||||
*/
|
*/
|
||||||
async function openContextMenuRestrictedNotebook(page) {
|
async function openContextMenuRestrictedNotebook(page) {
|
||||||
// Click text=Open MCT My Items (This expands the My Items folder to show it's chilren in the tree)
|
const myItemsFolder = page.locator('text=Open MCT My Items >> span').nth(3);
|
||||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
const className = await myItemsFolder.getAttribute('class');
|
||||||
|
if (!className.includes('c-disclosure-triangle--expanded')) {
|
||||||
//artifically wait to avoid mutation delay TODO: https://github.com/nasa/openmct/issues/5409
|
await myItemsFolder.click();
|
||||||
// eslint-disable-next-line playwright/no-wait-for-timeout
|
}
|
||||||
await page.waitForTimeout(1 * 1000);
|
|
||||||
|
|
||||||
// Click a:has-text("Unnamed CUSTOM_NAME")
|
// Click a:has-text("Unnamed CUSTOM_NAME")
|
||||||
await page.locator(`a:has-text("Unnamed ${CUSTOM_NAME}")`).click({
|
await page.locator(`a:has-text("Unnamed ${CUSTOM_NAME}")`).click({
|
||||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 21 KiB |
@ -28,11 +28,12 @@ const { test } = require('../../../fixtures.js');
|
|||||||
const { expect } = require('@playwright/test');
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('Handle missing object for plots', () => {
|
test.describe('Handle missing object for plots', () => {
|
||||||
test('Displays empty div for missing stacked plot item', async ({ page }) => {
|
test('Displays empty div for missing stacked plot item', async ({ page, browserName }) => {
|
||||||
|
test.fixme(browserName === 'firefox', 'Firefox failing due to console events being missed');
|
||||||
const errorLogs = [];
|
const errorLogs = [];
|
||||||
|
|
||||||
page.on("console", (message) => {
|
page.on("console", (message) => {
|
||||||
if (message.type() === 'warning') {
|
if (message.type() === 'warning' && message.text().includes('Missing domain object')) {
|
||||||
errorLogs.push(message.text());
|
errorLogs.push(message.text());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -71,7 +72,7 @@ test.describe('Handle missing object for plots', () => {
|
|||||||
//Check that there is only one stacked item plot with a plot, the missing one will be empty
|
//Check that there is only one stacked item plot with a plot, the missing one will be empty
|
||||||
await expect(page.locator(".c-plot--stacked-container:has(.gl-plot)")).toHaveCount(1);
|
await expect(page.locator(".c-plot--stacked-container:has(.gl-plot)")).toHaveCount(1);
|
||||||
//Verify that console.warn is thrown
|
//Verify that console.warn is thrown
|
||||||
await expect(errorLogs).toHaveLength(1);
|
expect(errorLogs).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -94,10 +95,6 @@ async function makeStackedPlot(page) {
|
|||||||
page.waitForSelector('.c-message-banner__message')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//Wait until Save Banner is gone
|
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
|
||||||
|
|
||||||
// save the stacked plot
|
// save the stacked plot
|
||||||
await saveStackedPlot(page);
|
await saveStackedPlot(page);
|
||||||
|
|
||||||
@ -155,7 +152,4 @@ async function createSineWaveGenerator(page) {
|
|||||||
//Wait for Save Banner to appear
|
//Wait for Save Banner to appear
|
||||||
page.waitForSelector('.c-message-banner__message')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
//Wait until Save Banner is gone
|
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
|
||||||
}
|
}
|
||||||
|
41
e2e/tests/plugins/remoteClock/remoteClock.e2e.spec.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
const { test } = require('../../../fixtures.js');
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
test.describe('Remote Clock', () => {
|
||||||
|
// eslint-disable-next-line require-await
|
||||||
|
test.fixme('blocks historical requests until first tick is received', async ({ page }) => {
|
||||||
|
test.info().annotations.push({
|
||||||
|
type: 'issue',
|
||||||
|
description: 'https://github.com/nasa/openmct/issues/5221'
|
||||||
|
});
|
||||||
|
// addInitScript to with remote clock
|
||||||
|
// Switch time conductor mode to 'remote clock'
|
||||||
|
// Navigate to telemetry
|
||||||
|
// Verify that the plot renders historical data within the correct bounds
|
||||||
|
// Refresh the page
|
||||||
|
// Verify again that the plot renders historical data within the correct bounds
|
||||||
|
});
|
||||||
|
});
|
@ -24,7 +24,7 @@ const { test } = require('../../../fixtures');
|
|||||||
const { expect } = require('@playwright/test');
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
test.describe('Telemetry Table', () => {
|
test.describe('Telemetry Table', () => {
|
||||||
test('unpauses when paused by button and user changes bounds', async ({ page }) => {
|
test('unpauses and filters data when paused by button and user changes bounds', async ({ page }) => {
|
||||||
test.info().annotations.push({
|
test.info().annotations.push({
|
||||||
type: 'issue',
|
type: 'issue',
|
||||||
description: 'https://github.com/nasa/openmct/issues/5113'
|
description: 'https://github.com/nasa/openmct/issues/5113'
|
||||||
@ -71,25 +71,34 @@ test.describe('Telemetry Table', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Click pause button
|
// Click pause button
|
||||||
const pauseButton = await page.locator('button.c-button.icon-pause');
|
const pauseButton = page.locator('button.c-button.icon-pause');
|
||||||
await pauseButton.click();
|
await pauseButton.click();
|
||||||
|
|
||||||
const tableWrapper = await page.locator('div.c-table-wrapper');
|
const tableWrapper = page.locator('div.c-table-wrapper');
|
||||||
await expect(tableWrapper).toHaveClass(/is-paused/);
|
await expect(tableWrapper).toHaveClass(/is-paused/);
|
||||||
|
|
||||||
// Arbitrarily change end date to some time in the future
|
// Subtract 5 minutes from the current end bound datetime and set it
|
||||||
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
|
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
|
||||||
await endTimeInput.click();
|
await endTimeInput.click();
|
||||||
|
|
||||||
let endDate = await endTimeInput.inputValue();
|
let endDate = await endTimeInput.inputValue();
|
||||||
endDate = new Date(endDate);
|
endDate = new Date(endDate);
|
||||||
endDate.setUTCDate(endDate.getUTCDate() + 1);
|
|
||||||
endDate = endDate.toISOString().replace(/T.*/, '');
|
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
|
||||||
|
endDate = endDate.toISOString().replace(/T/, ' ');
|
||||||
|
|
||||||
await endTimeInput.fill('');
|
await endTimeInput.fill('');
|
||||||
await endTimeInput.fill(endDate);
|
await endTimeInput.fill(endDate);
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
await expect(tableWrapper).not.toHaveClass(/is-paused/);
|
await expect(tableWrapper).not.toHaveClass(/is-paused/);
|
||||||
|
|
||||||
|
// Get the most recent telemetry date
|
||||||
|
const latestTelemetryDate = await page.locator('table.c-telemetry-table__body > tbody > tr').last().locator('td').nth(1).getAttribute('title');
|
||||||
|
|
||||||
|
// Verify that it is <= our new end bound
|
||||||
|
const latestMilliseconds = Date.parse(latestTelemetryDate);
|
||||||
|
const endBoundMilliseconds = Date.parse(endDate);
|
||||||
|
expect(latestMilliseconds).toBeLessThanOrEqual(endBoundMilliseconds);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -140,6 +140,7 @@ async function triggerTimer3dotMenuAction(page, action) {
|
|||||||
* @param {TimerViewAction} action
|
* @param {TimerViewAction} action
|
||||||
*/
|
*/
|
||||||
async function triggerTimerViewAction(page, action) {
|
async function triggerTimerViewAction(page, action) {
|
||||||
|
await page.locator('.c-timer').hover({trial: true});
|
||||||
const buttonTitle = buttonTitleFromAction(action);
|
const buttonTitle = buttonTitleFromAction(action);
|
||||||
await page.click(`button[title="${buttonTitle}"]`);
|
await page.click(`button[title="${buttonTitle}"]`);
|
||||||
assertTimerStateAfterAction(page, action);
|
assertTimerStateAfterAction(page, action);
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"cookies": [],
|
|
||||||
"origins": [
|
|
||||||
{
|
|
||||||
"origin": "http://localhost:8080",
|
|
||||||
"localStorage": [
|
|
||||||
{
|
|
||||||
"name": "mct-tree-expanded",
|
|
||||||
"value": "[]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "tcHistory",
|
|
||||||
"value": "{\"utc\":[{\"start\":1656473493306,\"end\":1656475293306},{\"start\":1655769110258,\"end\":1655770910258},{\"start\":1652301954635,\"end\":1652303754635}]}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "mct",
|
|
||||||
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"18ba28bf-152e-4e0f-9b9c-638fb2ade0c3\",\"namespace\":\"\"},{\"key\":\"fa64bd6c-9351-4d94-a54e-e062a93be3b6\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1656475294042,\"modified\":1656475294042},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"18ba28bf-152e-4e0f-9b9c-638fb2ade0c3\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"18ba28bf-152e-4e0f-9b9c-638fb2ade0c3\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"43cfb4b1-348c-43c0-a681-c4cf53b5335f\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1655770911020,\"location\":\"mine\",\"persisted\":1655770911020},\"fa64bd6c-9351-4d94-a54e-e062a93be3b6\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"fa64bd6c-9351-4d94-a54e-e062a93be3b6\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"26739ce0-9a56-466c-91dd-f08bd9bfc9d7\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1656475294040,\"location\":\"mine\",\"persisted\":1656475294040}}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -38,6 +38,8 @@ const sinon = require('sinon');
|
|||||||
|
|
||||||
const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken
|
const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken
|
||||||
|
|
||||||
|
const CUSTOM_NAME = 'CUSTOM_NAME';
|
||||||
|
|
||||||
// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758
|
// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758
|
||||||
// Will replace with cy.clock() equivalent
|
// Will replace with cy.clock() equivalent
|
||||||
test.beforeEach(async ({ context }) => {
|
test.beforeEach(async ({ context }) => {
|
||||||
@ -52,21 +54,23 @@ test.beforeEach(async ({ context }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Visual - Default Gauge is correct @addInit', async ({ page }) => {
|
test('Visual - Restricted Notebook is visually correct @addInit', async ({ page }) => {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
await page.addInitScript({ path: path.join(__dirname, '../plugins/gauge', './addInitGauge.js') });
|
await page.addInitScript({ path: path.join(__dirname, '../plugins/notebook', './addInitRestrictedNotebook.js') });
|
||||||
//Go to baseURL
|
//Go to baseURL
|
||||||
await page.goto('/', { waitUntil: 'networkidle' });
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
//Click the Create button
|
//Click the Create button
|
||||||
await page.click('button:has-text("Create")');
|
await page.click('button:has-text("Create")');
|
||||||
|
// Click text=CUSTOM_NAME
|
||||||
|
await page.click(`text=${CUSTOM_NAME}`);
|
||||||
|
// Click text=OK
|
||||||
|
await Promise.all([
|
||||||
|
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||||
|
page.click('text=OK')
|
||||||
|
]);
|
||||||
|
|
||||||
await page.click('text=Gauge');
|
// Take a snapshot of the newly created CUSTOM_NAME notebook
|
||||||
|
|
||||||
await page.click('text=OK');
|
|
||||||
|
|
||||||
// Take a snapshot of the newly created Gauge object
|
|
||||||
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
await percySnapshot(page, 'Default Gauge');
|
await percySnapshot(page, 'Restricted Notebook with CUSTOM_NAME');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -211,3 +211,22 @@ test('Visual - Display Layout Icon is correct', async ({ page }) => {
|
|||||||
await percySnapshot(page, 'Display Layout Create Menu');
|
await percySnapshot(page, 'Display Layout Create Menu');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Visual - Default Gauge is correct', async ({ page }) => {
|
||||||
|
|
||||||
|
//Go to baseURL
|
||||||
|
await page.goto('/', { waitUntil: 'networkidle' });
|
||||||
|
|
||||||
|
//Click the Create button
|
||||||
|
await page.click('button:has-text("Create")');
|
||||||
|
|
||||||
|
await page.click('text=Gauge');
|
||||||
|
|
||||||
|
await page.click('text=OK');
|
||||||
|
|
||||||
|
// Take a snapshot of the newly created Gauge object
|
||||||
|
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||||
|
await percySnapshot(page, 'Default Gauge');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
@ -52,9 +52,6 @@ test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
|
|||||||
//Wait for Save Banner to appear1
|
//Wait for Save Banner to appear1
|
||||||
page.waitForSelector('.c-message-banner__message')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
//Wait until Save Banner is gone
|
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
|
||||||
|
|
||||||
// save (exit edit mode)
|
// save (exit edit mode)
|
||||||
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click();
|
||||||
@ -69,18 +66,12 @@ test('Generate Visual Test Data @localStorage', async ({ page, context }) => {
|
|||||||
//Add a 5000 ms Delay
|
//Add a 5000 ms Delay
|
||||||
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
|
await page.locator('[aria-label="Loading Delay \\(ms\\)"]').fill('5000');
|
||||||
|
|
||||||
// Click on My Items in Tree. Workaround for https://github.com/nasa/openmct/issues/5184
|
|
||||||
await page.click('form[name="mctForm"] a:has-text("Overlay Plot")');
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('text=OK').click(),
|
page.locator('text=OK').click(),
|
||||||
//Wait for Save Banner to appear1
|
//Wait for Save Banner to appear1
|
||||||
page.waitForSelector('.c-message-banner__message')
|
page.waitForSelector('.c-message-banner__message')
|
||||||
]);
|
]);
|
||||||
//Wait until Save Banner is gone
|
|
||||||
await page.locator('.c-message-banner__close-button').click();
|
|
||||||
await page.waitForSelector('.c-message-banner__message', { state: 'detached'});
|
|
||||||
|
|
||||||
// focus the overlay plot
|
// focus the overlay plot
|
||||||
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
await page.locator('text=Open MCT My Items >> span').nth(3).click();
|
||||||
|
@ -31,7 +31,7 @@ const STATUSES = [{
|
|||||||
iconClassPoll: "icon-status-poll-question-mark"
|
iconClassPoll: "icon-status-poll-question-mark"
|
||||||
}, {
|
}, {
|
||||||
key: "GO",
|
key: "GO",
|
||||||
label: "GO",
|
label: "Go",
|
||||||
iconClass: "icon-check",
|
iconClass: "icon-check",
|
||||||
iconClassPoll: "icon-status-poll-question-mark",
|
iconClassPoll: "icon-status-poll-question-mark",
|
||||||
statusClass: "s-status-ok",
|
statusClass: "s-status-ok",
|
||||||
@ -39,7 +39,7 @@ const STATUSES = [{
|
|||||||
statusFgColor: "#000"
|
statusFgColor: "#000"
|
||||||
}, {
|
}, {
|
||||||
key: "MAYBE",
|
key: "MAYBE",
|
||||||
label: "MAYBE",
|
label: "Maybe",
|
||||||
iconClass: "icon-alert-triangle",
|
iconClass: "icon-alert-triangle",
|
||||||
iconClassPoll: "icon-status-poll-question-mark",
|
iconClassPoll: "icon-status-poll-question-mark",
|
||||||
statusClass: "s-status-warning",
|
statusClass: "s-status-warning",
|
||||||
@ -47,7 +47,7 @@ const STATUSES = [{
|
|||||||
statusFgColor: "#000"
|
statusFgColor: "#000"
|
||||||
}, {
|
}, {
|
||||||
key: "NO_GO",
|
key: "NO_GO",
|
||||||
label: "NO GO",
|
label: "No go",
|
||||||
iconClass: "icon-circle-slash",
|
iconClass: "icon-circle-slash",
|
||||||
iconClassPoll: "icon-status-poll-question-mark",
|
iconClassPoll: "icon-status-poll-question-mark",
|
||||||
statusClass: "s-status-error",
|
statusClass: "s-status-error",
|
||||||
|
@ -88,8 +88,8 @@
|
|||||||
"build:coverage": "webpack --config webpack.coverage.js",
|
"build:coverage": "webpack --config webpack.coverage.js",
|
||||||
"build:watch": "webpack --config webpack.dev.js --watch",
|
"build:watch": "webpack --config webpack.dev.js --watch",
|
||||||
"info": "npx envinfo --system --browsers --npmPackages --binaries --languages --markdown",
|
"info": "npx envinfo --system --browsers --npmPackages --binaries --languages --markdown",
|
||||||
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
"test": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
||||||
"test:firefox": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
|
"test:firefox": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run --browsers=FirefoxHeadless",
|
||||||
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
||||||
"test:e2e": "npx playwright test",
|
"test:e2e": "npx playwright test",
|
||||||
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags",
|
"test:e2e:ci": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome smoke branding default condition timeConductor clock exampleImagery persistence performance grandsearch notebook/tags",
|
||||||
@ -98,7 +98,7 @@
|
|||||||
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",
|
"test:e2e:visual": "percy exec --config ./e2e/.percy.yml -- npx playwright test --config=e2e/playwright-visual.config.js",
|
||||||
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
|
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js",
|
||||||
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
|
"test:perf": "npx playwright test --config=e2e/playwright-performance.config.js",
|
||||||
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
"test:watch": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
||||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2022/gm' ./src/ui/layout/AboutDialog.vue",
|
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2022/gm' ./src/ui/layout/AboutDialog.vue",
|
||||||
"update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2022/gm'",
|
"update-copyright-date": "npm run update-about-dialog-copyright && grep -lr --null --include=*.{js,scss,vue,ts,sh,html,md,frag} 'Copyright (c) 20' . | xargs -r0 perl -pi -e 's/Copyright\\s\\(c\\)\\s20\\d\\d\\-20\\d\\d/Copyright \\(c\\)\\ 2014\\-2022/gm'",
|
||||||
|
273
src/MCT.js
@ -96,161 +96,167 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.destroy = this.destroy.bind(this);
|
this.destroy = this.destroy.bind(this);
|
||||||
/**
|
[
|
||||||
* Tracks current selection state of the application.
|
/**
|
||||||
* @private
|
* Tracks current selection state of the application.
|
||||||
*/
|
* @private
|
||||||
this.selection = new Selection(this);
|
*/
|
||||||
|
['selection', () => new Selection(this)],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MCT's time conductor, which may be used to synchronize view contents
|
* MCT's time conductor, which may be used to synchronize view contents
|
||||||
* for telemetry- or time-based views.
|
* for telemetry- or time-based views.
|
||||||
* @type {module:openmct.TimeConductor}
|
* @type {module:openmct.TimeConductor}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name conductor
|
* @name conductor
|
||||||
*/
|
*/
|
||||||
this.time = new api.TimeAPI(this);
|
['time', () => new api.TimeAPI(this)],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for interacting with the composition of domain objects.
|
* An interface for interacting with the composition of domain objects.
|
||||||
* The composition of a domain object is the list of other domain
|
* The composition of a domain object is the list of other domain
|
||||||
* objects it "contains" (for instance, that should be displayed
|
* objects it "contains" (for instance, that should be displayed
|
||||||
* beneath it in the tree.)
|
* beneath it in the tree.)
|
||||||
*
|
*
|
||||||
* `composition` may be called as a function, in which case it acts
|
* `composition` may be called as a function, in which case it acts
|
||||||
* as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
|
* as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.CompositionAPI}
|
* @type {module:openmct.CompositionAPI}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name composition
|
* @name composition
|
||||||
*/
|
*/
|
||||||
this.composition = new api.CompositionAPI(this);
|
['composition', () => new api.CompositionAPI(this)],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for views of domain objects which should appear in the
|
* Registry for views of domain objects which should appear in the
|
||||||
* main viewing area.
|
* main viewing area.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.ViewRegistry}
|
* @type {module:openmct.ViewRegistry}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name objectViews
|
* @name objectViews
|
||||||
*/
|
*/
|
||||||
this.objectViews = new ViewRegistry();
|
['objectViews', () => new ViewRegistry()],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for views which should appear in the Inspector area.
|
* Registry for views which should appear in the Inspector area.
|
||||||
* These views will be chosen based on the selection state.
|
* These views will be chosen based on the selection state.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.InspectorViewRegistry}
|
* @type {module:openmct.InspectorViewRegistry}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name inspectorViews
|
* @name inspectorViews
|
||||||
*/
|
*/
|
||||||
this.inspectorViews = new InspectorViewRegistry();
|
['inspectorViews', () => new InspectorViewRegistry()],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for views which should appear in Edit Properties
|
* Registry for views which should appear in Edit Properties
|
||||||
* dialogs, and similar user interface elements used for
|
* dialogs, and similar user interface elements used for
|
||||||
* modifying domain objects external to its regular views.
|
* modifying domain objects external to its regular views.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.ViewRegistry}
|
* @type {module:openmct.ViewRegistry}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name propertyEditors
|
* @name propertyEditors
|
||||||
*/
|
*/
|
||||||
this.propertyEditors = new ViewRegistry();
|
['propertyEditors', () => new ViewRegistry()],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for views which should appear in the status indicator area.
|
* Registry for views which should appear in the toolbar area while
|
||||||
* @type {module:openmct.ViewRegistry}
|
* editing. These views will be chosen based on the selection state.
|
||||||
* @memberof module:openmct.MCT#
|
*
|
||||||
* @name indicators
|
* @type {module:openmct.ToolbarRegistry}
|
||||||
*/
|
* @memberof module:openmct.MCT#
|
||||||
this.indicators = new ViewRegistry();
|
* @name toolbars
|
||||||
|
*/
|
||||||
|
['toolbars', () => new ToolbarRegistry()],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for views which should appear in the toolbar area while
|
* Registry for domain object types which may exist within this
|
||||||
* editing. These views will be chosen based on the selection state.
|
* instance of Open MCT.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.ToolbarRegistry}
|
* @type {module:openmct.TypeRegistry}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name toolbars
|
* @name types
|
||||||
*/
|
*/
|
||||||
this.toolbars = new ToolbarRegistry();
|
['types', () => new api.TypeRegistry()],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registry for domain object types which may exist within this
|
* An interface for interacting with domain objects and the domain
|
||||||
* instance of Open MCT.
|
* object hierarchy.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.TypeRegistry}
|
* @type {module:openmct.ObjectAPI}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name types
|
* @name objects
|
||||||
*/
|
*/
|
||||||
this.types = new api.TypeRegistry();
|
['objects', () => new api.ObjectAPI.default(this.types, this)],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for interacting with domain objects and the domain
|
* An interface for retrieving and interpreting telemetry data associated
|
||||||
* object hierarchy.
|
* with a domain object.
|
||||||
*
|
*
|
||||||
* @type {module:openmct.ObjectAPI}
|
* @type {module:openmct.TelemetryAPI}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name objects
|
* @name telemetry
|
||||||
*/
|
*/
|
||||||
this.objects = new api.ObjectAPI.default(this.types, this);
|
['telemetry', () => new api.TelemetryAPI.default(this)],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for retrieving and interpreting telemetry data associated
|
* An interface for creating new indicators and changing them dynamically.
|
||||||
* with a domain object.
|
*
|
||||||
*
|
* @type {module:openmct.IndicatorAPI}
|
||||||
* @type {module:openmct.TelemetryAPI}
|
* @memberof module:openmct.MCT#
|
||||||
* @memberof module:openmct.MCT#
|
* @name indicators
|
||||||
* @name telemetry
|
*/
|
||||||
*/
|
['indicators', () => new api.IndicatorAPI(this)],
|
||||||
this.telemetry = new api.TelemetryAPI(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for creating new indicators and changing them dynamically.
|
* MCT's user awareness management, to enable user and
|
||||||
*
|
* role specific functionality.
|
||||||
* @type {module:openmct.IndicatorAPI}
|
* @type {module:openmct.UserAPI}
|
||||||
* @memberof module:openmct.MCT#
|
* @memberof module:openmct.MCT#
|
||||||
* @name indicators
|
* @name user
|
||||||
*/
|
*/
|
||||||
this.indicators = new api.IndicatorAPI(this);
|
['user', () => new api.UserAPI(this)],
|
||||||
|
|
||||||
/**
|
['notifications', () => new api.NotificationAPI()],
|
||||||
* MCT's user awareness management, to enable user and
|
|
||||||
* role specific functionality.
|
|
||||||
* @type {module:openmct.UserAPI}
|
|
||||||
* @memberof module:openmct.MCT#
|
|
||||||
* @name user
|
|
||||||
*/
|
|
||||||
this.user = new api.UserAPI(this);
|
|
||||||
|
|
||||||
this.notifications = new api.NotificationAPI();
|
['editor', () => new api.EditorAPI.default(this)],
|
||||||
|
|
||||||
this.editor = new api.EditorAPI.default(this);
|
['overlays', () => new OverlayAPI.default()],
|
||||||
|
|
||||||
this.overlays = new OverlayAPI.default();
|
['menus', () => new api.MenuAPI(this)],
|
||||||
|
|
||||||
this.menus = new api.MenuAPI(this);
|
['actions', () => new api.ActionsAPI(this)],
|
||||||
|
|
||||||
this.actions = new api.ActionsAPI(this);
|
['status', () => new api.StatusAPI(this)],
|
||||||
|
|
||||||
this.status = new api.StatusAPI(this);
|
['priority', () => api.PriorityAPI],
|
||||||
|
|
||||||
this.priority = api.PriorityAPI;
|
['router', () => new ApplicationRouter(this)],
|
||||||
|
|
||||||
this.router = new ApplicationRouter(this);
|
['faults', () => new api.FaultManagementAPI.default(this)],
|
||||||
this.faults = new api.FaultManagementAPI.default(this);
|
|
||||||
this.forms = new api.FormsAPI.default(this);
|
|
||||||
|
|
||||||
this.branding = BrandingAPI.default;
|
['forms', () => new api.FormsAPI.default(this)],
|
||||||
|
|
||||||
/**
|
['branding', () => BrandingAPI.default],
|
||||||
* MCT's annotation API that enables
|
|
||||||
* human-created comments and categorization linked to data products
|
/**
|
||||||
* @type {module:openmct.AnnotationAPI}
|
* MCT's annotation API that enables
|
||||||
* @memberof module:openmct.MCT#
|
* human-created comments and categorization linked to data products
|
||||||
* @name annotation
|
* @type {module:openmct.AnnotationAPI}
|
||||||
*/
|
* @memberof module:openmct.MCT#
|
||||||
this.annotation = new api.AnnotationAPI(this);
|
* @name annotation
|
||||||
|
*/
|
||||||
|
['annotation', () => new api.AnnotationAPI(this)]
|
||||||
|
].forEach(apiEntry => {
|
||||||
|
const apiName = apiEntry[0];
|
||||||
|
const apiObject = apiEntry[1]();
|
||||||
|
|
||||||
|
Object.defineProperty(this, apiName, {
|
||||||
|
value: apiObject,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Plugins that are installed by default
|
// Plugins that are installed by default
|
||||||
this.install(this.plugins.Plot());
|
this.install(this.plugins.Plot());
|
||||||
@ -281,6 +287,7 @@ define([
|
|||||||
this.install(this.plugins.ObjectInterceptors());
|
this.install(this.plugins.ObjectInterceptors());
|
||||||
this.install(this.plugins.DeviceClassifier());
|
this.install(this.plugins.DeviceClassifier());
|
||||||
this.install(this.plugins.UserIndicator());
|
this.install(this.plugins.UserIndicator());
|
||||||
|
this.install(this.plugins.Gauge());
|
||||||
}
|
}
|
||||||
|
|
||||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
@ -230,10 +230,15 @@ export default class ObjectAPI {
|
|||||||
return result;
|
return result;
|
||||||
}).catch((result) => {
|
}).catch((result) => {
|
||||||
console.warn(`Failed to retrieve ${keystring}:`, result);
|
console.warn(`Failed to retrieve ${keystring}:`, result);
|
||||||
|
this.openmct.notifications.error(`Failed to retrieve object ${keystring}`);
|
||||||
|
|
||||||
delete this.cache[keystring];
|
delete this.cache[keystring];
|
||||||
|
|
||||||
result = this.applyGetInterceptors(identifier);
|
if (!result) {
|
||||||
|
//no result means resource either doesn't exist or is missing
|
||||||
|
//otherwise it's an error, and we shouldn't apply interceptors
|
||||||
|
result = this.applyGetInterceptors(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@ -383,7 +388,13 @@ export default class ObjectAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result.catch((error) => {
|
||||||
|
if (error instanceof this.errors.Conflict) {
|
||||||
|
this.openmct.notifications.error(`Conflict detected while saving ${this.makeKeyString(domainObject.identifier)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,122 +20,18 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
const { TelemetryCollection } = require("./TelemetryCollection");
|
import TelemetryCollection from './TelemetryCollection';
|
||||||
|
import TelemetryRequestInterceptorRegistry from './TelemetryRequestInterceptor';
|
||||||
|
import CustomStringFormatter from '../../plugins/displayLayout/CustomStringFormatter';
|
||||||
|
import TelemetryMetadataManager from './TelemetryMetadataManager';
|
||||||
|
import TelemetryValueFormatter from './TelemetryValueFormatter';
|
||||||
|
import DefaultMetadataProvider from './DefaultMetadataProvider';
|
||||||
|
import objectUtils from 'objectUtils';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
define([
|
export default class TelemetryAPI {
|
||||||
'../../plugins/displayLayout/CustomStringFormatter',
|
|
||||||
'./TelemetryMetadataManager',
|
|
||||||
'./TelemetryValueFormatter',
|
|
||||||
'./DefaultMetadataProvider',
|
|
||||||
'objectUtils',
|
|
||||||
'lodash'
|
|
||||||
], function (
|
|
||||||
CustomStringFormatter,
|
|
||||||
TelemetryMetadataManager,
|
|
||||||
TelemetryValueFormatter,
|
|
||||||
DefaultMetadataProvider,
|
|
||||||
objectUtils,
|
|
||||||
_
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* A LimitEvaluator may be used to detect when telemetry values
|
|
||||||
* have exceeded nominal conditions.
|
|
||||||
*
|
|
||||||
* @interface LimitEvaluator
|
|
||||||
* @memberof module:openmct.TelemetryAPI~
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
constructor(openmct) {
|
||||||
* Check for any limit violations associated with a telemetry datum.
|
|
||||||
* @method evaluate
|
|
||||||
* @param {*} datum the telemetry datum to evaluate
|
|
||||||
* @param {TelemetryProperty} the property to check for limit violations
|
|
||||||
* @memberof module:openmct.TelemetryAPI~LimitEvaluator
|
|
||||||
* @returns {module:openmct.TelemetryAPI~LimitViolation} metadata about
|
|
||||||
* the limit violation, or undefined if a value is within limits
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A violation of limits defined for a telemetry property.
|
|
||||||
* @typedef LimitViolation
|
|
||||||
* @memberof {module:openmct.TelemetryAPI~}
|
|
||||||
* @property {string} cssClass the class (or space-separated classes) to
|
|
||||||
* apply to display elements for values which violate this limit
|
|
||||||
* @property {string} name the human-readable name for the limit violation
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A TelemetryFormatter converts telemetry values for purposes of
|
|
||||||
* display as text.
|
|
||||||
*
|
|
||||||
* @interface TelemetryFormatter
|
|
||||||
* @memberof module:openmct.TelemetryAPI~
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the 'key' from the datum and format it accordingly to
|
|
||||||
* telemetry metadata in domain object.
|
|
||||||
*
|
|
||||||
* @method format
|
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryFormatter#
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes a property which would be found in a datum of telemetry
|
|
||||||
* associated with a particular domain object.
|
|
||||||
*
|
|
||||||
* @typedef TelemetryProperty
|
|
||||||
* @memberof module:openmct.TelemetryAPI~
|
|
||||||
* @property {string} key the name of the property in the datum which
|
|
||||||
* contains this telemetry value
|
|
||||||
* @property {string} name the human-readable name for this property
|
|
||||||
* @property {string} [units] the units associated with this property
|
|
||||||
* @property {boolean} [temporal] true if this property is a timestamp, or
|
|
||||||
* may be otherwise used to order telemetry in a time-like
|
|
||||||
* fashion; default is false
|
|
||||||
* @property {boolean} [numeric] true if the values for this property
|
|
||||||
* can be interpreted plainly as numbers; default is true
|
|
||||||
* @property {boolean} [enumerated] true if this property may have only
|
|
||||||
* certain specific values; default is false
|
|
||||||
* @property {string} [values] for enumerated states, an ordered list
|
|
||||||
* of possible values
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes and bounds requests for telemetry data.
|
|
||||||
*
|
|
||||||
* @typedef TelemetryRequest
|
|
||||||
* @memberof module:openmct.TelemetryAPI~
|
|
||||||
* @property {string} sort the key of the property to sort by. This may
|
|
||||||
* be prefixed with a "+" or a "-" sign to sort in ascending
|
|
||||||
* or descending order respectively. If no prefix is present,
|
|
||||||
* ascending order will be used.
|
|
||||||
* @property {*} start the lower bound for values of the sorting property
|
|
||||||
* @property {*} end the upper bound for values of the sorting property
|
|
||||||
* @property {string[]} strategies symbolic identifiers for strategies
|
|
||||||
* (such as `minmax`) which may be recognized by providers;
|
|
||||||
* these will be tried in order until an appropriate provider
|
|
||||||
* is found
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides telemetry data. To connect to new data sources, new
|
|
||||||
* TelemetryProvider implementations should be
|
|
||||||
* [registered]{@link module:openmct.TelemetryAPI#addProvider}.
|
|
||||||
*
|
|
||||||
* @interface TelemetryProvider
|
|
||||||
* @memberof module:openmct.TelemetryAPI~
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for retrieving telemetry data associated with a domain
|
|
||||||
* object.
|
|
||||||
*
|
|
||||||
* @interface TelemetryAPI
|
|
||||||
* @augments module:openmct.TelemetryAPI~TelemetryProvider
|
|
||||||
* @memberof module:openmct
|
|
||||||
*/
|
|
||||||
function TelemetryAPI(openmct) {
|
|
||||||
this.openmct = openmct;
|
this.openmct = openmct;
|
||||||
|
|
||||||
this.formatMapCache = new WeakMap();
|
this.formatMapCache = new WeakMap();
|
||||||
@ -148,12 +44,14 @@ define([
|
|||||||
this.requestProviders = [];
|
this.requestProviders = [];
|
||||||
this.subscriptionProviders = [];
|
this.subscriptionProviders = [];
|
||||||
this.valueFormatterCache = new WeakMap();
|
this.valueFormatterCache = new WeakMap();
|
||||||
|
|
||||||
|
this.requestInterceptorRegistry = new TelemetryRequestInterceptorRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
TelemetryAPI.prototype.abortAllRequests = function () {
|
abortAllRequests() {
|
||||||
this.requestAbortControllers.forEach((controller) => controller.abort());
|
this.requestAbortControllers.forEach((controller) => controller.abort());
|
||||||
this.requestAbortControllers.clear();
|
this.requestAbortControllers.clear();
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return Custom String Formatter
|
* Return Custom String Formatter
|
||||||
@ -162,9 +60,9 @@ define([
|
|||||||
* @param {string} format custom formatter string (eg: %.4f, <s etc.)
|
* @param {string} format custom formatter string (eg: %.4f, <s etc.)
|
||||||
* @returns {CustomStringFormatter}
|
* @returns {CustomStringFormatter}
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.customStringFormatter = function (valueMetadata, format) {
|
customStringFormatter(valueMetadata, format) {
|
||||||
return new CustomStringFormatter.default(this.openmct, valueMetadata, format);
|
return new CustomStringFormatter(this.openmct, valueMetadata, format);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the given domainObject is a telemetry object. A telemetry
|
* Return true if the given domainObject is a telemetry object. A telemetry
|
||||||
@ -174,9 +72,9 @@ define([
|
|||||||
* @param {module:openmct.DomainObject} domainObject
|
* @param {module:openmct.DomainObject} domainObject
|
||||||
* @returns {boolean} true if the object is a telemetry object.
|
* @returns {boolean} true if the object is a telemetry object.
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.isTelemetryObject = function (domainObject) {
|
isTelemetryObject(domainObject) {
|
||||||
return Boolean(this.findMetadataProvider(domainObject));
|
return Boolean(this.findMetadataProvider(domainObject));
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this provider can supply telemetry data associated with
|
* Check if this provider can supply telemetry data associated with
|
||||||
@ -188,10 +86,10 @@ define([
|
|||||||
* @returns {boolean} true if telemetry can be provided
|
* @returns {boolean} true if telemetry can be provided
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
canProvideTelemetry(domainObject) {
|
||||||
return Boolean(this.findSubscriptionProvider(domainObject))
|
return Boolean(this.findSubscriptionProvider(domainObject))
|
||||||
|| Boolean(this.findRequestProvider(domainObject));
|
|| Boolean(this.findRequestProvider(domainObject));
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a telemetry provider with the telemetry service. This
|
* Register a telemetry provider with the telemetry service. This
|
||||||
@ -201,7 +99,7 @@ define([
|
|||||||
* @param {module:openmct.TelemetryAPI~TelemetryProvider} provider the new
|
* @param {module:openmct.TelemetryAPI~TelemetryProvider} provider the new
|
||||||
* telemetry provider
|
* telemetry provider
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.addProvider = function (provider) {
|
addProvider(provider) {
|
||||||
if (provider.supportsRequest) {
|
if (provider.supportsRequest) {
|
||||||
this.requestProviders.unshift(provider);
|
this.requestProviders.unshift(provider);
|
||||||
}
|
}
|
||||||
@ -217,54 +115,54 @@ define([
|
|||||||
if (provider.supportsLimits) {
|
if (provider.supportsLimits) {
|
||||||
this.limitProviders.unshift(provider);
|
this.limitProviders.unshift(provider);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.findSubscriptionProvider = function () {
|
findSubscriptionProvider() {
|
||||||
const args = Array.prototype.slice.apply(arguments);
|
const args = Array.prototype.slice.apply(arguments);
|
||||||
function supportsDomainObject(provider) {
|
function supportsDomainObject(provider) {
|
||||||
return provider.supportsSubscribe.apply(provider, args);
|
return provider.supportsSubscribe.apply(provider, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.subscriptionProviders.filter(supportsDomainObject)[0];
|
return this.subscriptionProviders.filter(supportsDomainObject)[0];
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.findRequestProvider = function (domainObject) {
|
findRequestProvider(domainObject) {
|
||||||
const args = Array.prototype.slice.apply(arguments);
|
const args = Array.prototype.slice.apply(arguments);
|
||||||
function supportsDomainObject(provider) {
|
function supportsDomainObject(provider) {
|
||||||
return provider.supportsRequest.apply(provider, args);
|
return provider.supportsRequest.apply(provider, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.requestProviders.filter(supportsDomainObject)[0];
|
return this.requestProviders.filter(supportsDomainObject)[0];
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.findMetadataProvider = function (domainObject) {
|
findMetadataProvider(domainObject) {
|
||||||
return this.metadataProviders.filter(function (p) {
|
return this.metadataProviders.filter(function (p) {
|
||||||
return p.supportsMetadata(domainObject);
|
return p.supportsMetadata(domainObject);
|
||||||
})[0];
|
})[0];
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.findLimitEvaluator = function (domainObject) {
|
findLimitEvaluator(domainObject) {
|
||||||
return this.limitProviders.filter(function (p) {
|
return this.limitProviders.filter(function (p) {
|
||||||
return p.supportsLimits(domainObject);
|
return p.supportsLimits(domainObject);
|
||||||
})[0];
|
})[0];
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.standardizeRequestOptions = function (options) {
|
standardizeRequestOptions(options) {
|
||||||
if (!Object.prototype.hasOwnProperty.call(options, 'start')) {
|
if (!Object.prototype.hasOwnProperty.call(options, 'start')) {
|
||||||
options.start = this.openmct.time.bounds().start;
|
options.start = this.openmct.time.bounds().start;
|
||||||
}
|
}
|
||||||
@ -276,7 +174,47 @@ define([
|
|||||||
if (!Object.prototype.hasOwnProperty.call(options, 'domain')) {
|
if (!Object.prototype.hasOwnProperty.call(options, 'domain')) {
|
||||||
options.domain = this.openmct.time.timeSystem().key;
|
options.domain = this.openmct.time.timeSystem().key;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a request interceptor that transforms a request via module:openmct.TelemetryAPI.request
|
||||||
|
* The request will be modifyed when it is received and will be returned in it's modified state
|
||||||
|
* The request will be transformed only if the interceptor is applicable to that domain object as defined by the RequestInterceptorDef
|
||||||
|
*
|
||||||
|
* @param {module:openmct.RequestInterceptorDef} requestInterceptorDef the request interceptor definition to add
|
||||||
|
* @method addRequestInterceptor
|
||||||
|
* @memberof module:openmct.TelemetryRequestInterceptorRegistry#
|
||||||
|
*/
|
||||||
|
addRequestInterceptor(requestInterceptorDef) {
|
||||||
|
this.requestInterceptorRegistry.addInterceptor(requestInterceptorDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the request interceptors for a given domain object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#getInterceptorsForRequest(identifier, request) {
|
||||||
|
return this.requestInterceptorRegistry.getInterceptors(identifier, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke interceptors if applicable for a given domain object.
|
||||||
|
*/
|
||||||
|
async applyRequestInterceptors(domainObject, request) {
|
||||||
|
const interceptors = this.#getInterceptorsForRequest(domainObject.identifier, request);
|
||||||
|
|
||||||
|
if (interceptors.length === 0) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiedRequest = { ...request };
|
||||||
|
|
||||||
|
for (let interceptor of interceptors) {
|
||||||
|
modifiedRequest = await interceptor.invoke(modifiedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiedRequest;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request telemetry collection for a domain object.
|
* Request telemetry collection for a domain object.
|
||||||
@ -292,13 +230,13 @@ define([
|
|||||||
* options for this telemetry collection request
|
* options for this telemetry collection request
|
||||||
* @returns {TelemetryCollection} a TelemetryCollection instance
|
* @returns {TelemetryCollection} a TelemetryCollection instance
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.requestCollection = function (domainObject, options = {}) {
|
requestCollection(domainObject, options = {}) {
|
||||||
return new TelemetryCollection(
|
return new TelemetryCollection(
|
||||||
this.openmct,
|
this.openmct,
|
||||||
domainObject,
|
domainObject,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request historical telemetry for a domain object.
|
* Request historical telemetry for a domain object.
|
||||||
@ -315,7 +253,7 @@ define([
|
|||||||
* @returns {Promise.<object[]>} a promise for an array of
|
* @returns {Promise.<object[]>} a promise for an array of
|
||||||
* telemetry data
|
* telemetry data
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.request = function (domainObject) {
|
async request(domainObject) {
|
||||||
if (this.noRequestProviderForAllObjects) {
|
if (this.noRequestProviderForAllObjects) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
@ -330,6 +268,7 @@ define([
|
|||||||
this.requestAbortControllers.add(abortController);
|
this.requestAbortControllers.add(abortController);
|
||||||
|
|
||||||
this.standardizeRequestOptions(arguments[1]);
|
this.standardizeRequestOptions(arguments[1]);
|
||||||
|
|
||||||
const provider = this.findRequestProvider.apply(this, arguments);
|
const provider = this.findRequestProvider.apply(this, arguments);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
this.requestAbortControllers.delete(abortController);
|
this.requestAbortControllers.delete(abortController);
|
||||||
@ -337,6 +276,8 @@ define([
|
|||||||
return this.handleMissingRequestProvider(domainObject);
|
return this.handleMissingRequestProvider(domainObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arguments[1] = await this.applyRequestInterceptors(domainObject, arguments[1]);
|
||||||
|
|
||||||
return provider.request.apply(provider, arguments)
|
return provider.request.apply(provider, arguments)
|
||||||
.catch((rejected) => {
|
.catch((rejected) => {
|
||||||
if (rejected.name !== 'AbortError') {
|
if (rejected.name !== 'AbortError') {
|
||||||
@ -348,7 +289,7 @@ define([
|
|||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.requestAbortControllers.delete(abortController);
|
this.requestAbortControllers.delete(abortController);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to realtime telemetry for a specific domain object.
|
* Subscribe to realtime telemetry for a specific domain object.
|
||||||
@ -364,7 +305,7 @@ define([
|
|||||||
* @returns {Function} a function which may be called to terminate
|
* @returns {Function} a function which may be called to terminate
|
||||||
* the subscription
|
* the subscription
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.subscribe = function (domainObject, callback, options) {
|
subscribe(domainObject, callback, options) {
|
||||||
const provider = this.findSubscriptionProvider(domainObject);
|
const provider = this.findSubscriptionProvider(domainObject);
|
||||||
|
|
||||||
if (!this.subscribeCache) {
|
if (!this.subscribeCache) {
|
||||||
@ -401,7 +342,7 @@ define([
|
|||||||
delete this.subscribeCache[keyString];
|
delete this.subscribeCache[keyString];
|
||||||
}
|
}
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get telemetry metadata for a given domain object. Returns a telemetry
|
* Get telemetry metadata for a given domain object. Returns a telemetry
|
||||||
@ -410,7 +351,7 @@ define([
|
|||||||
*
|
*
|
||||||
* @returns {TelemetryMetadataManager}
|
* @returns {TelemetryMetadataManager}
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getMetadata = function (domainObject) {
|
getMetadata(domainObject) {
|
||||||
if (!this.metadataCache.has(domainObject)) {
|
if (!this.metadataCache.has(domainObject)) {
|
||||||
const metadataProvider = this.findMetadataProvider(domainObject);
|
const metadataProvider = this.findMetadataProvider(domainObject);
|
||||||
if (!metadataProvider) {
|
if (!metadataProvider) {
|
||||||
@ -426,14 +367,14 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.metadataCache.get(domainObject);
|
return this.metadataCache.get(domainObject);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of valueMetadatas that are common to all supplied
|
* Return an array of valueMetadatas that are common to all supplied
|
||||||
* telemetry objects and match the requested hints.
|
* telemetry objects and match the requested hints.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.commonValuesForHints = function (metadatas, hints) {
|
commonValuesForHints(metadatas, hints) {
|
||||||
const options = metadatas.map(function (metadata) {
|
const options = metadatas.map(function (metadata) {
|
||||||
const values = metadata.valuesForHints(hints);
|
const values = metadata.valuesForHints(hints);
|
||||||
|
|
||||||
@ -453,14 +394,14 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
return _.sortBy(options, sortKeys);
|
return _.sortBy(options, sortKeys);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a value formatter for a given valueMetadata.
|
* Get a value formatter for a given valueMetadata.
|
||||||
*
|
*
|
||||||
* @returns {TelemetryValueFormatter}
|
* @returns {TelemetryValueFormatter}
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getValueFormatter = function (valueMetadata) {
|
getValueFormatter(valueMetadata) {
|
||||||
if (!this.valueFormatterCache.has(valueMetadata)) {
|
if (!this.valueFormatterCache.has(valueMetadata)) {
|
||||||
this.valueFormatterCache.set(
|
this.valueFormatterCache.set(
|
||||||
valueMetadata,
|
valueMetadata,
|
||||||
@ -469,7 +410,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.valueFormatterCache.get(valueMetadata);
|
return this.valueFormatterCache.get(valueMetadata);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a value formatter for a given key.
|
* Get a value formatter for a given key.
|
||||||
@ -477,9 +418,9 @@ define([
|
|||||||
*
|
*
|
||||||
* @returns {Format}
|
* @returns {Format}
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getFormatter = function (key) {
|
getFormatter(key) {
|
||||||
return this.formatters.get(key);
|
return this.formatters.get(key);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a format map of all value formatters for a given piece of telemetry
|
* Get a format map of all value formatters for a given piece of telemetry
|
||||||
@ -487,7 +428,7 @@ define([
|
|||||||
*
|
*
|
||||||
* @returns {Object<String, {TelemetryValueFormatter}>}
|
* @returns {Object<String, {TelemetryValueFormatter}>}
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getFormatMap = function (metadata) {
|
getFormatMap(metadata) {
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -502,14 +443,14 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.formatMapCache.get(metadata);
|
return this.formatMapCache.get(metadata);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error Handling: Missing Request provider
|
* Error Handling: Missing Request provider
|
||||||
*
|
*
|
||||||
* @returns Promise
|
* @returns Promise
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.handleMissingRequestProvider = function (domainObject) {
|
handleMissingRequestProvider(domainObject) {
|
||||||
this.noRequestProviderForAllObjects = this.requestProviders.every(requestProvider => {
|
this.noRequestProviderForAllObjects = this.requestProviders.every(requestProvider => {
|
||||||
const supportsRequest = requestProvider.supportsRequest.apply(requestProvider, arguments);
|
const supportsRequest = requestProvider.supportsRequest.apply(requestProvider, arguments);
|
||||||
const hasRequestProvider = Object.prototype.hasOwnProperty.call(requestProvider, 'request') && typeof requestProvider.request === 'function';
|
const hasRequestProvider = Object.prototype.hasOwnProperty.call(requestProvider, 'request') && typeof requestProvider.request === 'function';
|
||||||
@ -532,15 +473,15 @@ define([
|
|||||||
console.warn(detailMessage);
|
console.warn(detailMessage);
|
||||||
|
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new telemetry data formatter.
|
* Register a new telemetry data formatter.
|
||||||
* @param {Format} format the
|
* @param {Format} format the
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.addFormat = function (format) {
|
addFormat(format) {
|
||||||
this.formatters.set(format.key, format);
|
this.formatters.set(format.key, format);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limit evaluator for this domain object.
|
* Get a limit evaluator for this domain object.
|
||||||
@ -558,9 +499,9 @@ define([
|
|||||||
* @method limitEvaluator
|
* @method limitEvaluator
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.limitEvaluator = function (domainObject) {
|
limitEvaluator(domainObject) {
|
||||||
return this.getLimitEvaluator(domainObject);
|
return this.getLimitEvaluator(domainObject);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limits for this domain object.
|
* Get a limits for this domain object.
|
||||||
@ -578,9 +519,9 @@ define([
|
|||||||
* @method limits
|
* @method limits
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.limitDefinition = function (domainObject) {
|
limitDefinition(domainObject) {
|
||||||
return this.getLimits(domainObject);
|
return this.getLimits(domainObject);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limit evaluator for this domain object.
|
* Get a limit evaluator for this domain object.
|
||||||
@ -598,7 +539,7 @@ define([
|
|||||||
* @method limitEvaluator
|
* @method limitEvaluator
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getLimitEvaluator = function (domainObject) {
|
getLimitEvaluator(domainObject) {
|
||||||
const provider = this.findLimitEvaluator(domainObject);
|
const provider = this.findLimitEvaluator(domainObject);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return {
|
return {
|
||||||
@ -607,7 +548,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
return provider.getLimitEvaluator(domainObject);
|
return provider.getLimitEvaluator(domainObject);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a limit definitions for this domain object.
|
* Get a limit definitions for this domain object.
|
||||||
@ -636,7 +577,7 @@ define([
|
|||||||
* supported colors are purple, red, orange, yellow and cyan
|
* supported colors are purple, red, orange, yellow and cyan
|
||||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||||
*/
|
*/
|
||||||
TelemetryAPI.prototype.getLimits = function (domainObject) {
|
getLimits(domainObject) {
|
||||||
const provider = this.findLimitEvaluator(domainObject);
|
const provider = this.findLimitEvaluator(domainObject);
|
||||||
if (!provider || !provider.getLimits) {
|
if (!provider || !provider.getLimits) {
|
||||||
return {
|
return {
|
||||||
@ -647,7 +588,104 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
return provider.getLimits(domainObject);
|
return provider.getLimits(domainObject);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TelemetryAPI;
|
/**
|
||||||
});
|
* A LimitEvaluator may be used to detect when telemetry values
|
||||||
|
* have exceeded nominal conditions.
|
||||||
|
*
|
||||||
|
* @interface LimitEvaluator
|
||||||
|
* @memberof module:openmct.TelemetryAPI~
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for any limit violations associated with a telemetry datum.
|
||||||
|
* @method evaluate
|
||||||
|
* @param {*} datum the telemetry datum to evaluate
|
||||||
|
* @param {TelemetryProperty} the property to check for limit violations
|
||||||
|
* @memberof module:openmct.TelemetryAPI~LimitEvaluator
|
||||||
|
* @returns {module:openmct.TelemetryAPI~LimitViolation} metadata about
|
||||||
|
* the limit violation, or undefined if a value is within limits
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A violation of limits defined for a telemetry property.
|
||||||
|
* @typedef LimitViolation
|
||||||
|
* @memberof {module:openmct.TelemetryAPI~}
|
||||||
|
* @property {string} cssClass the class (or space-separated classes) to
|
||||||
|
* apply to display elements for values which violate this limit
|
||||||
|
* @property {string} name the human-readable name for the limit violation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A TelemetryFormatter converts telemetry values for purposes of
|
||||||
|
* display as text.
|
||||||
|
*
|
||||||
|
* @interface TelemetryFormatter
|
||||||
|
* @memberof module:openmct.TelemetryAPI~
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the 'key' from the datum and format it accordingly to
|
||||||
|
* telemetry metadata in domain object.
|
||||||
|
*
|
||||||
|
* @method format
|
||||||
|
* @memberof module:openmct.TelemetryAPI~TelemetryFormatter#
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes a property which would be found in a datum of telemetry
|
||||||
|
* associated with a particular domain object.
|
||||||
|
*
|
||||||
|
* @typedef TelemetryProperty
|
||||||
|
* @memberof module:openmct.TelemetryAPI~
|
||||||
|
* @property {string} key the name of the property in the datum which
|
||||||
|
* contains this telemetry value
|
||||||
|
* @property {string} name the human-readable name for this property
|
||||||
|
* @property {string} [units] the units associated with this property
|
||||||
|
* @property {boolean} [temporal] true if this property is a timestamp, or
|
||||||
|
* may be otherwise used to order telemetry in a time-like
|
||||||
|
* fashion; default is false
|
||||||
|
* @property {boolean} [numeric] true if the values for this property
|
||||||
|
* can be interpreted plainly as numbers; default is true
|
||||||
|
* @property {boolean} [enumerated] true if this property may have only
|
||||||
|
* certain specific values; default is false
|
||||||
|
* @property {string} [values] for enumerated states, an ordered list
|
||||||
|
* of possible values
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes and bounds requests for telemetry data.
|
||||||
|
*
|
||||||
|
* @typedef TelemetryRequest
|
||||||
|
* @memberof module:openmct.TelemetryAPI~
|
||||||
|
* @property {string} sort the key of the property to sort by. This may
|
||||||
|
* be prefixed with a "+" or a "-" sign to sort in ascending
|
||||||
|
* or descending order respectively. If no prefix is present,
|
||||||
|
* ascending order will be used.
|
||||||
|
* @property {*} start the lower bound for values of the sorting property
|
||||||
|
* @property {*} end the upper bound for values of the sorting property
|
||||||
|
* @property {string[]} strategies symbolic identifiers for strategies
|
||||||
|
* (such as `minmax`) which may be recognized by providers;
|
||||||
|
* these will be tried in order until an appropriate provider
|
||||||
|
* is found
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides telemetry data. To connect to new data sources, new
|
||||||
|
* TelemetryProvider implementations should be
|
||||||
|
* [registered]{@link module:openmct.TelemetryAPI#addProvider}.
|
||||||
|
*
|
||||||
|
* @interface TelemetryProvider
|
||||||
|
* @memberof module:openmct.TelemetryAPI~
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for retrieving telemetry data associated with a domain
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* @interface TelemetryAPI
|
||||||
|
* @augments module:openmct.TelemetryAPI~TelemetryProvider
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
import { createOpenMct, resetApplicationState } from 'utils/testing';
|
||||||
import TelemetryAPI from './TelemetryAPI';
|
import TelemetryAPI from './TelemetryAPI';
|
||||||
const { TelemetryCollection } = require("./TelemetryCollection");
|
import TelemetryCollection from './TelemetryCollection';
|
||||||
|
|
||||||
describe('Telemetry API', function () {
|
describe('Telemetry API', function () {
|
||||||
let openmct;
|
let openmct;
|
||||||
|
@ -26,7 +26,7 @@ import { LOADED_ERROR, TIMESYSTEM_KEY_NOTIFICATION, TIMESYSTEM_KEY_WARNING } fro
|
|||||||
|
|
||||||
/** Class representing a Telemetry Collection. */
|
/** Class representing a Telemetry Collection. */
|
||||||
|
|
||||||
export class TelemetryCollection extends EventEmitter {
|
export default class TelemetryCollection extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* Creates a Telemetry Collection
|
* Creates a Telemetry Collection
|
||||||
*
|
*
|
||||||
@ -49,6 +49,7 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
this.pageState = undefined;
|
this.pageState = undefined;
|
||||||
this.lastBounds = undefined;
|
this.lastBounds = undefined;
|
||||||
this.requestAbort = undefined;
|
this.requestAbort = undefined;
|
||||||
|
this.isStrategyLatest = this.options.strategy === 'latest';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +127,8 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
this.requestAbort = new AbortController();
|
this.requestAbort = new AbortController();
|
||||||
options.signal = this.requestAbort.signal;
|
options.signal = this.requestAbort.signal;
|
||||||
this.emit('requestStarted');
|
this.emit('requestStarted');
|
||||||
historicalData = await historicalProvider.request(this.domainObject, options);
|
const modifiedOptions = await this.openmct.telemetry.applyRequestInterceptors(this.domainObject, options);
|
||||||
|
historicalData = await historicalProvider.request(this.domainObject, modifiedOptions);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name !== 'AbortError') {
|
if (error.name !== 'AbortError') {
|
||||||
console.error('Error requesting telemetry data...');
|
console.error('Error requesting telemetry data...');
|
||||||
@ -168,17 +170,18 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_processNewTelemetry(telemetryData) {
|
_processNewTelemetry(telemetryData) {
|
||||||
performance.mark('tlm:process:start');
|
|
||||||
if (telemetryData === undefined) {
|
if (telemetryData === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let latestBoundedDatum = this.boundedTelemetry[this.boundedTelemetry.length - 1];
|
||||||
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
|
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
|
||||||
let parsedValue;
|
let parsedValue;
|
||||||
let beforeStartOfBounds;
|
let beforeStartOfBounds;
|
||||||
let afterEndOfBounds;
|
let afterEndOfBounds;
|
||||||
let added = [];
|
let added = [];
|
||||||
|
|
||||||
|
// loop through, sort and dedupe
|
||||||
for (let datum of data) {
|
for (let datum of data) {
|
||||||
parsedValue = this.parseTime(datum);
|
parsedValue = this.parseTime(datum);
|
||||||
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
beforeStartOfBounds = parsedValue < this.lastBounds.start;
|
||||||
@ -218,7 +221,17 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (added.length) {
|
if (added.length) {
|
||||||
this.emit('add', added);
|
// if latest strategy is requested, we need to check if the value is the latest unmitted value
|
||||||
|
if (this.isStrategyLatest) {
|
||||||
|
this.boundedTelemetry = [this.boundedTelemetry[this.boundedTelemetry.length - 1]];
|
||||||
|
|
||||||
|
// if true, then this value has yet to be emitted
|
||||||
|
if (this.boundedTelemetry[0] !== latestBoundedDatum) {
|
||||||
|
this.emit('add', this.boundedTelemetry);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.emit('add', added);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,13 +291,20 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
|
|
||||||
if (startChanged) {
|
if (startChanged) {
|
||||||
testDatum[this.timeKey] = bounds.start;
|
testDatum[this.timeKey] = bounds.start;
|
||||||
// Calculate the new index of the first item within the bounds
|
|
||||||
startIndex = _.sortedIndexBy(
|
// a little more complicated if not latest strategy
|
||||||
this.boundedTelemetry,
|
if (!this.isStrategyLatest) {
|
||||||
testDatum,
|
// Calculate the new index of the first item within the bounds
|
||||||
datum => this.parseTime(datum)
|
startIndex = _.sortedIndexBy(
|
||||||
);
|
this.boundedTelemetry,
|
||||||
discarded = this.boundedTelemetry.splice(0, startIndex);
|
testDatum,
|
||||||
|
datum => this.parseTime(datum)
|
||||||
|
);
|
||||||
|
discarded = this.boundedTelemetry.splice(0, startIndex);
|
||||||
|
} else if (this.parseTime(testDatum) > this.parseTime(this.boundedTelemetry[0])) {
|
||||||
|
discarded = this.boundedTelemetry;
|
||||||
|
this.boundedTelemetry = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endChanged) {
|
if (endChanged) {
|
||||||
@ -296,7 +316,6 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
datum => this.parseTime(datum)
|
datum => this.parseTime(datum)
|
||||||
);
|
);
|
||||||
added = this.futureBuffer.splice(0, endIndex);
|
added = this.futureBuffer.splice(0, endIndex);
|
||||||
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discarded.length > 0) {
|
if (discarded.length > 0) {
|
||||||
@ -304,6 +323,13 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (added.length > 0) {
|
if (added.length > 0) {
|
||||||
|
if (!this.isStrategyLatest) {
|
||||||
|
this.boundedTelemetry = [...this.boundedTelemetry, ...added];
|
||||||
|
} else {
|
||||||
|
added = [added[added.length - 1]];
|
||||||
|
this.boundedTelemetry = added;
|
||||||
|
}
|
||||||
|
|
||||||
this.emit('add', added);
|
this.emit('add', added);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -362,7 +388,6 @@ export class TelemetryCollection extends EventEmitter {
|
|||||||
* @todo handle subscriptions more granually
|
* @todo handle subscriptions more granually
|
||||||
*/
|
*/
|
||||||
_reset() {
|
_reset() {
|
||||||
performance.mark('tlm:reset');
|
|
||||||
this.boundedTelemetry = [];
|
this.boundedTelemetry = [];
|
||||||
this.futureBuffer = [];
|
this.futureBuffer = [];
|
||||||
|
|
||||||
|
68
src/api/telemetry/TelemetryRequestInterceptor.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
export default class TelemetryRequestInterceptorRegistry {
|
||||||
|
/**
|
||||||
|
* A TelemetryRequestInterceptorRegistry maintains the definitions for different interceptors that may be invoked on telemetry
|
||||||
|
* requests.
|
||||||
|
* @interface TelemetryRequestInterceptorRegistry
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.interceptors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface TelemetryRequestInterceptorDef
|
||||||
|
* @property {function} appliesTo function that determines if this interceptor should be called for the given identifier/request
|
||||||
|
* @property {function} invoke function that transforms the provided request and returns the transformed request
|
||||||
|
* @property {function} priority the priority for this interceptor. A higher number returned has more weight than a lower number
|
||||||
|
* @memberof module:openmct TelemetryRequestInterceptorRegistry#
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new telemetry request interceptor.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.RequestInterceptorDef} requestInterceptorDef the interceptor to add
|
||||||
|
* @method addInterceptor
|
||||||
|
* @memberof module:openmct.TelemetryRequestInterceptorRegistry#
|
||||||
|
*/
|
||||||
|
addInterceptor(interceptorDef) {
|
||||||
|
//TODO: sort by priority
|
||||||
|
this.interceptors.push(interceptorDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all interceptors applicable to a domain object/request.
|
||||||
|
* @method getInterceptors
|
||||||
|
* @returns [module:openmct.RequestInterceptorDef] the registered interceptors for this identifier/request
|
||||||
|
* @memberof module:openmct.TelemetryRequestInterceptorRegistry#
|
||||||
|
*/
|
||||||
|
getInterceptors(identifier, request) {
|
||||||
|
return this.interceptors.filter(interceptor => {
|
||||||
|
return typeof interceptor.appliesTo === 'function'
|
||||||
|
&& interceptor.appliesTo(identifier, request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -197,7 +197,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setUnit() {
|
setUnit() {
|
||||||
this.unit = this.valueMetadata.unit || '';
|
this.unit = this.valueMetadata ? this.valueMetadata.unit : '';
|
||||||
},
|
},
|
||||||
firstNonDomainAttribute(metadata) {
|
firstNonDomainAttribute(metadata) {
|
||||||
return metadata
|
return metadata
|
||||||
|
@ -83,9 +83,12 @@ export default {
|
|||||||
for (let ladTable of ladTables) {
|
for (let ladTable of ladTables) {
|
||||||
for (let telemetryObject of ladTable) {
|
for (let telemetryObject of ladTable) {
|
||||||
let metadata = this.openmct.telemetry.getMetadata(telemetryObject.domainObject);
|
let metadata = this.openmct.telemetry.getMetadata(telemetryObject.domainObject);
|
||||||
for (let metadatum of metadata.valueMetadatas) {
|
|
||||||
if (metadatum.unit) {
|
if (metadata) {
|
||||||
return true;
|
for (let metadatum of metadata.valueMetadatas) {
|
||||||
|
if (metadatum.unit) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -178,6 +178,26 @@ export default {
|
|||||||
this.requestDataFor(telemetryObject);
|
this.requestDataFor(telemetryObject);
|
||||||
this.subscribeToObject(telemetryObject);
|
this.subscribeToObject(telemetryObject);
|
||||||
},
|
},
|
||||||
|
setTrace(key, name, axisMetadata, xValues, yValues) {
|
||||||
|
let trace = {
|
||||||
|
key,
|
||||||
|
name: name,
|
||||||
|
x: xValues,
|
||||||
|
y: yValues,
|
||||||
|
xAxisMetadata: {},
|
||||||
|
yAxisMetadata: axisMetadata.yAxisMetadata,
|
||||||
|
type: this.domainObject.configuration.useBar ? 'bar' : 'scatter',
|
||||||
|
mode: 'lines',
|
||||||
|
line: {
|
||||||
|
shape: this.domainObject.configuration.useInterpolation
|
||||||
|
},
|
||||||
|
marker: {
|
||||||
|
color: this.domainObject.configuration.barStyles.series[key].color
|
||||||
|
},
|
||||||
|
hoverinfo: this.domainObject.configuration.useBar ? 'skip' : 'x+y'
|
||||||
|
};
|
||||||
|
this.addTrace(trace, key);
|
||||||
|
},
|
||||||
addTrace(trace, key) {
|
addTrace(trace, key) {
|
||||||
if (!this.trace.length) {
|
if (!this.trace.length) {
|
||||||
this.trace = this.trace.concat([trace]);
|
this.trace = this.trace.concat([trace]);
|
||||||
@ -236,7 +256,15 @@ export default {
|
|||||||
refreshData(bounds, isTick) {
|
refreshData(bounds, isTick) {
|
||||||
if (!isTick) {
|
if (!isTick) {
|
||||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
const telemetryObjects = Object.values(this.telemetryObjects);
|
||||||
telemetryObjects.forEach(this.requestDataFor);
|
telemetryObjects.forEach((telemetryObject) => {
|
||||||
|
//clear existing data
|
||||||
|
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||||
|
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
||||||
|
this.setTrace(key, telemetryObject.name, axisMetadata, [], []);
|
||||||
|
//request new data
|
||||||
|
this.requestDataFor(telemetryObject);
|
||||||
|
this.subscribeToObject(telemetryObject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeAllSubscriptions() {
|
removeAllSubscriptions() {
|
||||||
@ -320,25 +348,7 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let trace = {
|
this.setTrace(key, telemetryObject.name, axisMetadata, xValues, yValues);
|
||||||
key,
|
|
||||||
name: telemetryObject.name,
|
|
||||||
x: xValues,
|
|
||||||
y: yValues,
|
|
||||||
xAxisMetadata: xAxisMetadata,
|
|
||||||
yAxisMetadata: axisMetadata.yAxisMetadata,
|
|
||||||
type: this.domainObject.configuration.useBar ? 'bar' : 'scatter',
|
|
||||||
mode: 'lines',
|
|
||||||
line: {
|
|
||||||
shape: this.domainObject.configuration.useInterpolation
|
|
||||||
},
|
|
||||||
marker: {
|
|
||||||
color: this.domainObject.configuration.barStyles.series[key].color
|
|
||||||
},
|
|
||||||
hoverinfo: this.domainObject.configuration.useBar ? 'skip' : 'x+y'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addTrace(trace, key);
|
|
||||||
},
|
},
|
||||||
isDataInTimeRange(datum, key, telemetryObject) {
|
isDataInTimeRange(datum, key, telemetryObject) {
|
||||||
const timeSystemKey = this.timeContext.timeSystem().key;
|
const timeSystemKey = this.timeContext.timeSystem().key;
|
||||||
|
@ -66,12 +66,15 @@ export default function BarGraphViewProvider(openmct) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
template: '<bar-graph-view :options="options"></bar-graph-view>'
|
template: '<bar-graph-view ref="graphComponent" :options="options"></bar-graph-view>'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
component.$destroy();
|
component.$destroy();
|
||||||
component = undefined;
|
component = undefined;
|
||||||
|
},
|
||||||
|
onClearData() {
|
||||||
|
component.$refs.graphComponent.refreshData();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -281,11 +281,11 @@ export default {
|
|||||||
this.xKeyOptions.push(
|
this.xKeyOptions.push(
|
||||||
metadataValues.reduce((previousValue, currentValue) => {
|
metadataValues.reduce((previousValue, currentValue) => {
|
||||||
return {
|
return {
|
||||||
name: `${previousValue.name}, ${currentValue.name}`,
|
name: previousValue?.name ? `${previousValue.name}, ${currentValue.name}` : `${currentValue.name}`,
|
||||||
value: currentValue.key,
|
value: currentValue.key,
|
||||||
isArrayValue: currentValue.isArrayValue
|
isArrayValue: currentValue.isArrayValue
|
||||||
};
|
};
|
||||||
})
|
}, {name: ''})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,11 +316,16 @@ export default {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.yKey === undefined) {
|
if (this.yKey === undefined) {
|
||||||
yKeyOptionIndex = this.yKeyOptions.findIndex((option, index) => index !== xKeyOptionIndex);
|
if (metadataValues.length && metadataArrayValues.length === 0) {
|
||||||
if (yKeyOptionIndex > -1) {
|
|
||||||
update = true;
|
update = true;
|
||||||
this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
|
this.yKey = 'none';
|
||||||
this.yKeyLabel = this.yKeyOptions[yKeyOptionIndex].name;
|
} else {
|
||||||
|
yKeyOptionIndex = this.yKeyOptions.findIndex((option, index) => index !== xKeyOptionIndex);
|
||||||
|
if (yKeyOptionIndex > -1) {
|
||||||
|
update = true;
|
||||||
|
this.yKey = this.yKeyOptions[yKeyOptionIndex].value;
|
||||||
|
this.yKeyLabel = this.yKeyOptions[yKeyOptionIndex].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,6 +341,8 @@ export default {
|
|||||||
|
|
||||||
return option;
|
return option;
|
||||||
});
|
});
|
||||||
|
} else if (this.xKey !== undefined && this.domainObject.configuration.axes.yKey === undefined) {
|
||||||
|
this.domainObject.configuration.axes.yKey = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.xKeyOptions = this.xKeyOptions.map((option, index) => {
|
this.xKeyOptions = this.xKeyOptions.map((option, index) => {
|
||||||
|
@ -28,9 +28,9 @@ export default function () {
|
|||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
openmct.types.addType(BAR_GRAPH_KEY, {
|
openmct.types.addType(BAR_GRAPH_KEY, {
|
||||||
key: BAR_GRAPH_KEY,
|
key: BAR_GRAPH_KEY,
|
||||||
name: "Graph (Bar or Line)",
|
name: "Graph",
|
||||||
cssClass: "icon-bar-chart",
|
cssClass: "icon-bar-chart",
|
||||||
description: "View data as a bar graph. Can be added to Display Layouts.",
|
description: "Visualize data as a bar or line graph.",
|
||||||
creatable: true,
|
creatable: true,
|
||||||
initialize: function (domainObject) {
|
initialize: function (domainObject) {
|
||||||
domainObject.composition = [];
|
domainObject.composition = [];
|
||||||
|
@ -367,19 +367,26 @@ describe("the plugin", function () {
|
|||||||
type: "test-object",
|
type: "test-object",
|
||||||
name: "Test Object",
|
name: "Test Object",
|
||||||
telemetry: {
|
telemetry: {
|
||||||
values: [{
|
values: [
|
||||||
key: "some-key",
|
{
|
||||||
name: "Some attribute",
|
key: "some-key",
|
||||||
hints: {
|
source: "some-key",
|
||||||
domain: 1
|
name: "Some attribute",
|
||||||
}
|
format: "enum",
|
||||||
}, {
|
enumerations: [
|
||||||
key: "some-other-key",
|
{
|
||||||
name: "Another attribute",
|
value: 0,
|
||||||
hints: {
|
string: "OFF"
|
||||||
range: 1
|
},
|
||||||
}
|
{
|
||||||
}]
|
value: 1,
|
||||||
|
string: "ON"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
hints: {
|
||||||
|
range: 1
|
||||||
|
}
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const composition = openmct.composition.get(parent);
|
const composition = openmct.composition.get(parent);
|
||||||
|
@ -300,8 +300,11 @@ export default class ConditionManager extends EventEmitter {
|
|||||||
return this.compositionLoad.then(() => {
|
return this.compositionLoad.then(() => {
|
||||||
let latestTimestamp;
|
let latestTimestamp;
|
||||||
let conditionResults = {};
|
let conditionResults = {};
|
||||||
|
let nextLegOptions = {...options};
|
||||||
|
delete nextLegOptions.onPartialResponse;
|
||||||
|
|
||||||
const conditionRequests = this.conditions
|
const conditionRequests = this.conditions
|
||||||
.map(condition => condition.requestLADConditionResult(options));
|
.map(condition => condition.requestLADConditionResult(nextLegOptions));
|
||||||
|
|
||||||
return Promise.all(conditionRequests)
|
return Promise.all(conditionRequests)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
|
@ -21,23 +21,25 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-fault-mgmt__list-header c-fault-mgmt__list">
|
<div class="c-fault-mgmt-item-header c-fault-mgmt__list-header c-fault-mgmt__list">
|
||||||
<div class="c-fault-mgmt__checkbox">
|
<div class="c-fault-mgmt-item-header c-fault-mgmt__checkbox">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="isSelectAll"
|
:checked="isSelectAll"
|
||||||
@input="selectAll"
|
@input="selectAll"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-fault-mgmt__list-content">
|
<div class="c-fault-mgmt-item-header c-fault-mgmt__list-header-results c-fault-mgmt__list-severity">
|
||||||
<div class="c-fault-mgmt__list-header-results"> {{ totalFaultsCount }} Results </div>
|
{{ totalFaultsCount }} Results
|
||||||
|
</div>
|
||||||
|
<div class="c-fault-mgmt__list-header-content">
|
||||||
<div class="c-fault-mgmt__list-content-right">
|
<div class="c-fault-mgmt__list-content-right">
|
||||||
<div class="c-fault-mgmt__list-header-tripVal c-fault-mgmt__list-trigVal">Trip Value</div>
|
<div class="c-fault-mgmt-item-header c-fault-mgmt__list-header-tripVal">Trip Value</div>
|
||||||
<div class="c-fault-mgmt__list-header-liveVal c-fault-mgmt__list-curVal">Live Value</div>
|
<div class="c-fault-mgmt-item-header c-fault-mgmt__list-header-liveVal">Live Value</div>
|
||||||
<div class="c-fault-mgmt__list-header-trigTime c-fault-mgmt__list-trigTime">Trigger Time</div>
|
<div class="c-fault-mgmt-item-header c-fault-mgmt__list-header-trigTime">Trigger Time</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-fault-mgmt__list-action-wrapper">
|
<div class=" c-fault-mgmt-item-header c-fault-mgmt__list-header-action-wrapper">
|
||||||
<div class="c-fault-mgmt__list-header-sortButton c-fault-mgmt__list-action-button">
|
<div class="c-fault-mgmt__list-header-sortButton c-fault-mgmt__list-action-button">
|
||||||
<SelectField
|
<SelectField
|
||||||
class="c-fault-mgmt-viewButton"
|
class="c-fault-mgmt-viewButton"
|
||||||
|
@ -23,52 +23,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="c-fault-mgmt__list data-selectable"
|
class="c-fault-mgmt__list data-selectable"
|
||||||
:class="[
|
:class="classesFromState"
|
||||||
{'is-selected': isSelected},
|
|
||||||
{'is-unacknowledged': !fault.acknowledged},
|
|
||||||
{'is-shelved': fault.shelved}
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
<div class="c-fault-mgmt__checkbox">
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-checkbox">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="isSelected"
|
:checked="isSelected"
|
||||||
@input="toggleSelected"
|
@input="toggleSelected"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="c-fault-mgmt-item">
|
||||||
class="c-fault-mgmt__list-severity"
|
<div
|
||||||
:title="fault.severity"
|
class="c-fault-mgmt__list-severity"
|
||||||
:class="[
|
:title="fault.severity"
|
||||||
'is-severity-' + severity
|
:class="[
|
||||||
]"
|
'is-severity-' + severity
|
||||||
>
|
]"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-fault-mgmt__list-content">
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-content">
|
||||||
<div class="c-fault-mgmt__list-pathname">
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-pathname">
|
||||||
<div class="c-fault-mgmt__list-path">{{ fault.namespace }}</div>
|
<div class="c-fault-mgmt__list-path">{{ fault.namespace }}</div>
|
||||||
<div class="c-fault-mgmt__list-faultname">{{ fault.name }}</div>
|
<div class="c-fault-mgmt__list-faultname">{{ fault.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-fault-mgmt__list-content-right">
|
<div class="c-fault-mgmt__list-content-right">
|
||||||
<div
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-trigVal">
|
||||||
class="c-fault-mgmt__list-trigVal"
|
<div
|
||||||
:class="tripValueClassname"
|
class="c-fault-mgmt-item__value"
|
||||||
title="Trip Value"
|
:class="tripValueClassname"
|
||||||
>{{ fault.triggerValueInfo.value }}</div>
|
title="Trip Value"
|
||||||
<div
|
>{{ fault.triggerValueInfo.value }}</div>
|
||||||
class="c-fault-mgmt__list-curVal"
|
|
||||||
:class="liveValueClassname"
|
|
||||||
title="Live Value"
|
|
||||||
>
|
|
||||||
{{ fault.currentValueInfo.value }}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-curVal">
|
||||||
class="c-fault-mgmt__list-trigTime"
|
<div
|
||||||
>{{ fault.triggerTime }}
|
class="c-fault-mgmt-item__value"
|
||||||
|
:class="liveValueClassname"
|
||||||
|
title="Live Value"
|
||||||
|
>{{ fault.currentValueInfo.value }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-trigTime">
|
||||||
|
<div
|
||||||
|
class="c-fault-mgmt-item__value"
|
||||||
|
title="Last Trigger Time"
|
||||||
|
>{{ fault.triggerTime }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-fault-mgmt__list-action-wrapper">
|
<div class="c-fault-mgmt-item c-fault-mgmt__list-action-wrapper">
|
||||||
<button
|
<button
|
||||||
class="c-fault-mgmt__list-action-button l-browse-bar__actions c-icon-button icon-3-dots"
|
class="c-fault-mgmt__list-action-button l-browse-bar__actions c-icon-button icon-3-dots"
|
||||||
title="Disposition Actions"
|
title="Disposition Actions"
|
||||||
@ -77,7 +80,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const RANGE_CONDITION_CLASS = {
|
const RANGE_CONDITION_CLASS = {
|
||||||
@ -106,6 +108,36 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
classesFromState() {
|
||||||
|
const exclusiveStates = [
|
||||||
|
{
|
||||||
|
className: 'is-shelved',
|
||||||
|
test: () => this.fault.shelved
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'is-unacknowledged',
|
||||||
|
test: () => !this.fault.acknowledged && !this.fault.shelved
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'is-acknowledged',
|
||||||
|
test: () => this.fault.acknowledged && !this.fault.shelved
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const classes = [];
|
||||||
|
|
||||||
|
if (this.isSelected) {
|
||||||
|
classes.push('is-selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingState = exclusiveStates.find(stateDefinition => stateDefinition.test());
|
||||||
|
|
||||||
|
if (matchingState !== undefined) {
|
||||||
|
classes.push(matchingState.className);
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
liveValueClassname() {
|
liveValueClassname() {
|
||||||
const currentValueInfo = this.fault?.currentValueInfo;
|
const currentValueInfo = this.fault?.currentValueInfo;
|
||||||
if (!currentValueInfo || currentValueInfo.monitoringResult === 'IN_LIMITS') {
|
if (!currentValueInfo || currentValueInfo.monitoringResult === 'IN_LIMITS') {
|
||||||
@ -149,7 +181,7 @@ export default {
|
|||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
cssClass: 'icon-bell',
|
cssClass: 'icon-check',
|
||||||
isDisabled: this.fault.acknowledged,
|
isDisabled: this.fault.acknowledged,
|
||||||
name: 'Acknowledge',
|
name: 'Acknowledge',
|
||||||
description: '',
|
description: '',
|
||||||
|
@ -35,25 +35,31 @@
|
|||||||
@shelveSelected="toggleShelveSelected"
|
@shelveSelected="toggleShelveSelected"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FaultManagementListHeader
|
<div class="c-faults-list-view-header-item-container-wrapper">
|
||||||
class="header"
|
<div class="c-faults-list-view-header-item-container">
|
||||||
:selected-faults="Object.values(selectedFaults)"
|
<FaultManagementListHeader
|
||||||
:total-faults-count="filteredFaultsList.length"
|
class="header"
|
||||||
@selectAll="selectAll"
|
:selected-faults="Object.values(selectedFaults)"
|
||||||
@sortChanged="sortChanged"
|
:total-faults-count="filteredFaultsList.length"
|
||||||
/>
|
@selectAll="selectAll"
|
||||||
|
@sortChanged="sortChanged"
|
||||||
|
/>
|
||||||
|
|
||||||
<template v-if="filteredFaultsList.length > 0">
|
<div class="c-faults-list-view-item-body">
|
||||||
<FaultManagementListItem
|
<template v-if="filteredFaultsList.length > 0">
|
||||||
v-for="fault of filteredFaultsList"
|
<FaultManagementListItem
|
||||||
:key="fault.id"
|
v-for="fault of filteredFaultsList"
|
||||||
:fault="fault"
|
:key="fault.id"
|
||||||
:is-selected="isSelected(fault)"
|
:fault="fault"
|
||||||
@toggleSelected="toggleSelected"
|
:is-selected="isSelected(fault)"
|
||||||
@acknowledgeSelected="toggleAcknowledgeSelected"
|
@toggleSelected="toggleSelected"
|
||||||
@shelveSelected="toggleShelveSelected"
|
@acknowledgeSelected="toggleAcknowledgeSelected"
|
||||||
/>
|
@shelveSelected="toggleShelveSelected"
|
||||||
</template>
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -90,17 +96,19 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
filteredFaultsList() {
|
filteredFaultsList() {
|
||||||
const filterName = FILTER_ITEMS[this.filterIndex];
|
const filterName = FILTER_ITEMS[this.filterIndex];
|
||||||
let list = this.faultsList.filter(fault => !fault.shelved);
|
let list = this.faultsList;
|
||||||
|
|
||||||
|
// Exclude shelved alarms from all views except the Shelved view
|
||||||
|
if (filterName !== 'Shelved') {
|
||||||
|
list = list.filter(fault => fault.shelved !== true);
|
||||||
|
}
|
||||||
|
|
||||||
if (filterName === 'Acknowledged') {
|
if (filterName === 'Acknowledged') {
|
||||||
list = this.faultsList.filter(fault => fault.acknowledged);
|
list = list.filter(fault => fault.acknowledged);
|
||||||
}
|
} else if (filterName === 'Unacknowledged') {
|
||||||
|
list = list.filter(fault => !fault.acknowledged);
|
||||||
if (filterName === 'Unacknowledged') {
|
} else if (filterName === 'Shelved') {
|
||||||
list = this.faultsList.filter(fault => !fault.acknowledged);
|
list = list.filter(fault => fault.shelved);
|
||||||
}
|
|
||||||
|
|
||||||
if (filterName === 'Shelved') {
|
|
||||||
list = this.faultsList.filter(fault => fault.shelved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.searchTerm.length > 0) {
|
if (this.searchTerm.length > 0) {
|
||||||
@ -195,7 +203,7 @@ export default {
|
|||||||
{
|
{
|
||||||
key: 'comment',
|
key: 'comment',
|
||||||
control: 'textarea',
|
control: 'textarea',
|
||||||
name: 'Comment',
|
name: 'Optional comment',
|
||||||
pattern: '\\S+',
|
pattern: '\\S+',
|
||||||
required: false,
|
required: false,
|
||||||
cssClass: 'l-input-lg',
|
cssClass: 'l-input-lg',
|
||||||
@ -237,7 +245,7 @@ export default {
|
|||||||
{
|
{
|
||||||
key: 'comment',
|
key: 'comment',
|
||||||
control: 'textarea',
|
control: 'textarea',
|
||||||
name: 'Comment',
|
name: 'Optional comment',
|
||||||
pattern: '\\S+',
|
pattern: '\\S+',
|
||||||
required: false,
|
required: false,
|
||||||
cssClass: 'l-input-lg',
|
cssClass: 'l-input-lg',
|
||||||
@ -246,7 +254,7 @@ export default {
|
|||||||
{
|
{
|
||||||
key: 'shelveDuration',
|
key: 'shelveDuration',
|
||||||
control: 'select',
|
control: 'select',
|
||||||
name: 'Shelve Duration',
|
name: 'Shelve duration',
|
||||||
options: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS,
|
options: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS,
|
||||||
required: false,
|
required: false,
|
||||||
cssClass: 'l-input-lg',
|
cssClass: 'l-input-lg',
|
||||||
|
@ -32,7 +32,7 @@ export default function FaultManagementPlugin() {
|
|||||||
name: 'Fault Management',
|
name: 'Fault Management',
|
||||||
creatable: false,
|
creatable: false,
|
||||||
description: 'Fault Management View',
|
description: 'Fault Management View',
|
||||||
cssClass: 'icon-telemetry'
|
cssClass: 'icon-bell'
|
||||||
});
|
});
|
||||||
|
|
||||||
openmct.objectViews.addProvider(new FaultManagementViewProvider(openmct));
|
openmct.objectViews.addProvider(new FaultManagementViewProvider(openmct));
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="c-fault-mgmt__toolbar">
|
<div class="c-fault-mgmt__toolbar">
|
||||||
<button
|
<button
|
||||||
class="c-icon-button icon-bell"
|
class="c-icon-button icon-check"
|
||||||
title="Acknowledge selected faults"
|
title="Acknowledge selected faults"
|
||||||
:disabled="disableAcknowledge"
|
:disabled="disableAcknowledge"
|
||||||
@click="acknowledgeSelected"
|
@click="acknowledgeSelected"
|
||||||
|
@ -21,14 +21,13 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="c-fault-mgmt">
|
<FaultManagementListView
|
||||||
<FaultManagementListView
|
:faults-list="faultsList"
|
||||||
:faults-list="faultsList"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import FaultManagementListView from './FaultManagementListView.vue';
|
import FaultManagementListView from './FaultManagementListView.vue';
|
||||||
import { FAULT_MANAGEMENT_ALARMS, FAULT_MANAGEMENT_GLOBAL_ALARMS } from './constants';
|
import { FAULT_MANAGEMENT_ALARMS, FAULT_MANAGEMENT_GLOBAL_ALARMS } from './constants';
|
||||||
|
|
||||||
|
@ -19,65 +19,32 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
$colorFaultItemFg: $colorBodyFg;
|
||||||
/*********************************************** FAULT PROPERTIES */
|
$colorFaultItemFgEmphasis: $colorBodyFgEm;
|
||||||
.is-severity-critical{
|
$colorFaultItemBg: pullForward($colorBodyBg, 5%);
|
||||||
@include glyphBefore($glyph-icon-alert-triangle);
|
|
||||||
color: $colorStatusError;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-severity-warning{
|
|
||||||
@include glyphBefore($glyph-icon-alert-rect);
|
|
||||||
color: $colorStatusAlert;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-severity-watch{
|
|
||||||
@include glyphBefore($glyph-icon-info);
|
|
||||||
color: $colorCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-unacknowledged{
|
|
||||||
.c-fault-mgmt__list-severity{
|
|
||||||
@include pulse($animName: severityAnim, $dur: 200ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-selected {
|
|
||||||
background: $colorSelectedBg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-shelved{
|
|
||||||
.c-fault-mgmt__list-content{
|
|
||||||
opacity: 50% !important;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.c-fault-mgmt__list-severity{
|
|
||||||
@include pulse($animName: shelvedAnim, $dur: 0ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*********************************************** SEARCH */
|
/*********************************************** SEARCH */
|
||||||
.c-fault-mgmt__search-row{
|
.c-fault-mgmt__search-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-fault-mgmt-search{
|
.c-fault-mgmt-search {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************** TOOLBAR */
|
/*********************************************** TOOLBAR */
|
||||||
.c-fault-mgmt__toolbar{
|
.c-fault-mgmt__toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
> * {
|
flex: 0 0 auto;
|
||||||
font-size: 1.25em;
|
> * + * {
|
||||||
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,150 +52,217 @@
|
|||||||
.c-faults-list-view {
|
.c-faults-list-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-top: $interiorMargin;
|
margin-top: $interiorMargin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.c-faults-list-view-header-item-container {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
grid-template-columns: max-content max-content repeat(5,minmax(max-content, 20%)) max-content;
|
||||||
|
grid-row-gap: $interiorMargin;
|
||||||
|
|
||||||
/*********************************************** FAULT ITEM */
|
&-wrapper {
|
||||||
.c-fault-mgmt__list{
|
flex: 1 1 auto;
|
||||||
background: rgba($colorBodyFg, 0.1);
|
padding-right: $interiorMargin; // Fend of from scrollbar
|
||||||
margin-bottom: 5px;
|
overflow-y: auto;
|
||||||
padding: 4px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
margin-left: $interiorMargin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-severity{
|
.--width-less-than-600 & {
|
||||||
|
grid-template-columns: max-content max-content 1fr 1fr max-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-faults-list-view-item-body {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************** LIST */
|
||||||
|
.c-fault-mgmt__list {
|
||||||
|
display: contents;
|
||||||
|
color: $colorFaultItemFg;
|
||||||
|
|
||||||
|
&-checkbox{
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-severity {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
margin-left: $interiorMarginLg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-pathname{
|
&.is-severity-critical {
|
||||||
flex-wrap: wrap;
|
@include glyphBefore($glyph-icon-alert-triangle);
|
||||||
flex: 1 1 auto;
|
color: $colorStatusError;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
&.is-severity-warning {
|
||||||
&-path{
|
@include glyphBefore($glyph-icon-alert-rect);
|
||||||
font-size: .75em;
|
color: $colorStatusAlert;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-faultname{
|
&.is-severity-watch {
|
||||||
font-weight: bold;
|
@include glyphBefore($glyph-icon-info);
|
||||||
font-size: 1.3em;
|
color: $colorCommand;
|
||||||
}
|
|
||||||
|
|
||||||
&-content{
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content-right{
|
|
||||||
margin-left: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-trigVal, &-curVal, &-trigTime{
|
|
||||||
@include ellipsize;
|
|
||||||
border-radius: $controlCr;
|
|
||||||
padding: $interiorMargin;
|
|
||||||
width: 80px;
|
|
||||||
margin-right: $interiorMarginLg;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
&-trigVal {
|
|
||||||
@include isLimit();
|
|
||||||
background: rgba($colorBodyFg, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-curVal {
|
|
||||||
@include isLimit();
|
|
||||||
background: rgba($colorBodyFg, 0.25);
|
|
||||||
&-alert{
|
|
||||||
background: $colorWarningHi;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-trigTime{
|
&-content {
|
||||||
width: auto;
|
display: contents;
|
||||||
|
|
||||||
|
.--width-less-than-600 & {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-action-wrapper{
|
&-pathname {
|
||||||
display: flex;
|
padding-right: $interiorMarginLg;
|
||||||
align-content: right;
|
overflow-wrap: anywhere;
|
||||||
width: 100px;
|
min-width: 100px;
|
||||||
|
|
||||||
|
}
|
||||||
|
&-path {
|
||||||
|
font-size: .85em;
|
||||||
|
margin-left: $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-action-button{
|
&-faultname{
|
||||||
|
font-size: 1.3em;
|
||||||
|
margin-left: $interiorMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content-right {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-trigTime {
|
||||||
|
grid-column: 6 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action-wrapper {
|
||||||
|
text-align: right;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action-button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
justify-content: right;
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATES
|
||||||
|
&.is-unacknowledged {
|
||||||
|
color: $colorFaultItemFgEmphasis;
|
||||||
|
.c-fault-mgmt__list-severity {
|
||||||
|
@include pulse($animName: severityAnim, $dur: 200ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-acknowledged,
|
||||||
|
&.is-shelved {
|
||||||
|
.c-fault-mgmt__list-severity {
|
||||||
|
&:before {
|
||||||
|
opacity: 60%;
|
||||||
|
//font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
color: $colorFaultItemFgEmphasis;
|
||||||
|
display: block;
|
||||||
|
font-family: symbolsfont;
|
||||||
|
position: absolute;
|
||||||
|
//text-shadow: black 0 0 2px;
|
||||||
|
right: -3px;
|
||||||
|
bottom: -3px;
|
||||||
|
transform-origin: right bottom;
|
||||||
|
transform: scale(0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-shelved {
|
||||||
|
.c-fault-mgmt__list-pathname {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-acknowledged .c-fault-mgmt__list-severity:after {
|
||||||
|
content: $glyph-icon-check;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-shelved .c-fault-mgmt__list-severity:after {
|
||||||
|
content: $glyph-icon-timer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************** LIST HEADER */
|
/*********************************************** LIST HEADER */
|
||||||
.c-fault-mgmt__list-header{
|
.c-fault-mgmt__list-header {
|
||||||
display: flex;
|
display: contents;
|
||||||
background: rgba($colorBodyFg, .23);
|
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&-tripVal, &-liveVal, &-trigTime{
|
* {
|
||||||
background: none;
|
margin: 0px;
|
||||||
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-trigTime{
|
.--width-less-than-600 & {
|
||||||
width: 160px;
|
.c-fault-mgmt__list-content-right {
|
||||||
}
|
display:none;
|
||||||
&-sortButton{
|
}
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-left: auto;
|
|
||||||
justify-content: right;
|
|
||||||
display: flex;
|
|
||||||
align-content: right;
|
|
||||||
width: 100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
&-content {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
.is-severity-critical{
|
&-results {
|
||||||
@include glyphBefore($glyph-icon-alert-triangle);
|
grid-column: 2 / span 2;
|
||||||
color: $colorStatusError;
|
font-size: 1em;
|
||||||
}
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.is-severity-warning{
|
&-action-wrapper {
|
||||||
@include glyphBefore($glyph-icon-alert-rect);
|
grid-column: 7 / span 2;
|
||||||
color: $colorStatusAlert;
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-severity-watch{
|
.--width-less-than-600 & {
|
||||||
@include glyphBefore($glyph-icon-info);
|
grid-column: 4 / span 2;
|
||||||
color: $colorCommand;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.is-unacknowledged{
|
|
||||||
.c-fault-mgmt__list-severity{
|
|
||||||
@include pulse($animName: severityAnim, $dur: 200ms);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-selected {
|
/*********************************************** GRID ITEM */
|
||||||
background: $colorSelectedBg;
|
.c-fault-mgmt-item {
|
||||||
}
|
$p: $interiorMargin;
|
||||||
|
padding: $p;
|
||||||
|
background: $colorFaultItemBg;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
.is-shelved{
|
&-header {
|
||||||
.c-fault-mgmt__list-content{
|
$c: $colorBodyBg;
|
||||||
opacity: 60% !important;
|
background: $c;
|
||||||
font-style: italic;
|
border-bottom: 5px solid $c; // Creates illusion of "space" beneath header
|
||||||
|
min-height: 30px; // Needed to align cells
|
||||||
|
padding: $p;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.c-fault-mgmt__list-severity{
|
|
||||||
@include pulse($animName: shelvedAnim, $dur: 0ms);
|
&__value {
|
||||||
|
@include isLimit();
|
||||||
|
background: rgba($colorBodyFg, 0.1);
|
||||||
|
padding: $p;
|
||||||
|
border-radius: $controlCr;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-selected & {
|
||||||
|
background: $colorSelectedBg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,7 @@
|
|||||||
</g>
|
</g>
|
||||||
<g class="c-dial__text">
|
<g class="c-dial__text">
|
||||||
<text
|
<text
|
||||||
|
v-if="displayUnits"
|
||||||
x="50%"
|
x="50%"
|
||||||
y="70%"
|
y="70%"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<div class="c-form__row">
|
<div class="c-form__row">
|
||||||
<span class="req-indicator req">
|
<span class="req-indicator req">
|
||||||
</span>
|
</span>
|
||||||
<label>Range minimum value</label>
|
<label>Minimum value</label>
|
||||||
<input
|
<input
|
||||||
ref="min"
|
ref="min"
|
||||||
v-model.number="min"
|
v-model.number="min"
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<div class="c-form__row">
|
<div class="c-form__row">
|
||||||
<span class="req-indicator">
|
<span class="req-indicator">
|
||||||
</span>
|
</span>
|
||||||
<label>Range low limit</label>
|
<label>Low limit</label>
|
||||||
<input
|
<input
|
||||||
ref="limitLow"
|
ref="limitLow"
|
||||||
v-model.number="limitLow"
|
v-model.number="limitLow"
|
||||||
@ -64,26 +64,26 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="c-form__row">
|
<div class="c-form__row">
|
||||||
<span class="req-indicator req">
|
<span class="req-indicator">
|
||||||
</span>
|
</span>
|
||||||
<label>Range maximum value</label>
|
<label>High limit</label>
|
||||||
<input
|
<input
|
||||||
ref="max"
|
ref="limitHigh"
|
||||||
v-model.number="max"
|
v-model.number="limitHigh"
|
||||||
data-field-name="max"
|
data-field-name="limitHigh"
|
||||||
type="number"
|
type="number"
|
||||||
@input="onChange"
|
@input="onChange"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="c-form__row">
|
<div class="c-form__row">
|
||||||
<span class="req-indicator">
|
<span class="req-indicator req">
|
||||||
</span>
|
</span>
|
||||||
<label>Range high limit</label>
|
<label>Maximum value</label>
|
||||||
<input
|
<input
|
||||||
ref="limitHigh"
|
ref="max"
|
||||||
v-model.number="limitHigh"
|
v-model.number="max"
|
||||||
data-field-name="limitHigh"
|
data-field-name="max"
|
||||||
type="number"
|
type="number"
|
||||||
@input="onChange"
|
@input="onChange"
|
||||||
>
|
>
|
||||||
|
@ -210,9 +210,10 @@
|
|||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: $interiorMargin;
|
padding: $interiorMargin;
|
||||||
width: min-content;
|
width: max-content;
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-left: $interiorMargin;
|
margin-left: $interiorMargin;
|
||||||
@ -338,7 +339,6 @@
|
|||||||
&__input {
|
&__input {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
color: rgba($colorMenuFg, 0.5);
|
color: rgba($colorMenuFg, 0.5);
|
||||||
@ -353,13 +353,16 @@
|
|||||||
|
|
||||||
&--filters {
|
&--filters {
|
||||||
// Styles specific to the brightness and contrast controls
|
// Styles specific to the brightness and contrast controls
|
||||||
|
|
||||||
.c-image-controls {
|
.c-image-controls {
|
||||||
|
&__controls {
|
||||||
|
width: 80px; // About the minimum this element can be; cannot size based on % due to markup structure
|
||||||
|
}
|
||||||
|
|
||||||
&__sliders {
|
&__sliders {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 80px;
|
width: 100%;
|
||||||
|
|
||||||
> * + * {
|
> * + * {
|
||||||
margin-top: 11px;
|
margin-top: 11px;
|
||||||
|
@ -76,7 +76,10 @@ export default {
|
|||||||
dataRemoved(dataToRemove) {
|
dataRemoved(dataToRemove) {
|
||||||
this.imageHistory = this.imageHistory.filter(existingDatum => {
|
this.imageHistory = this.imageHistory.filter(existingDatum => {
|
||||||
const shouldKeep = dataToRemove.some(datumToRemove => {
|
const shouldKeep = dataToRemove.some(datumToRemove => {
|
||||||
return (existingDatum.utc !== datumToRemove.utc);
|
const existingDatumTimestamp = this.parseTime(existingDatum);
|
||||||
|
const datumToRemoveTimestamp = this.parseTime(datumToRemove);
|
||||||
|
|
||||||
|
return (existingDatumTimestamp !== datumToRemoveTimestamp);
|
||||||
});
|
});
|
||||||
|
|
||||||
return shouldKeep;
|
return shouldKeep;
|
||||||
|
@ -373,39 +373,30 @@ describe("The Imagery View Layouts", () => {
|
|||||||
return Vue.nextTick();
|
return Vue.nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("on mount should show the the most recent image", () => {
|
it("on mount should show the the most recent image", async () => {
|
||||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||||
return Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("on mount should show the any image layers", (done) => {
|
it("on mount should show the any image layers", async () => {
|
||||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||||
Vue.nextTick().then(() => {
|
await Vue.nextTick();
|
||||||
Vue.nextTick(() => {
|
const layerEls = parent.querySelectorAll('.js-layer-image');
|
||||||
const layerEls = parent.querySelectorAll('.js-layer-image');
|
console.log(layerEls);
|
||||||
console.log(layerEls);
|
expect(layerEls.length).toEqual(1);
|
||||||
expect(layerEls.length).toEqual(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show the clicked thumbnail as the main image", (done) => {
|
it("should show the clicked thumbnail as the main image", async () => {
|
||||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const target = imageTelemetry[5].url;
|
const target = imageTelemetry[5].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
xit("should show that an image is new", (done) => {
|
xit("should show that an image is new", (done) => {
|
||||||
@ -424,71 +415,60 @@ describe("The Imagery View Layouts", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show that an image is not new", (done) => {
|
it("should show that an image is not new", async () => {
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const target = imageTelemetry[4].url;
|
const target = imageTelemetry[4].url;
|
||||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageIsNew = isNew(parent);
|
const imageIsNew = isNew(parent);
|
||||||
|
|
||||||
expect(imageIsNew).toBeFalse();
|
expect(imageIsNew).toBeFalse();
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate via arrow keys", (done) => {
|
it("should navigate via arrow keys", async () => {
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
let keyOpts = {
|
const keyOpts = {
|
||||||
element: parent.querySelector('.c-imagery'),
|
element: parent.querySelector('.c-imagery'),
|
||||||
key: 'ArrowLeft',
|
key: 'ArrowLeft',
|
||||||
keyCode: 37,
|
keyCode: 37,
|
||||||
type: 'keyup'
|
type: 'keyup'
|
||||||
};
|
};
|
||||||
|
|
||||||
simulateKeyEvent(keyOpts);
|
simulateKeyEvent(keyOpts);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should navigate via numerous arrow keys", (done) => {
|
it("should navigate via numerous arrow keys", async () => {
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
let element = parent.querySelector('.c-imagery');
|
const element = parent.querySelector('.c-imagery');
|
||||||
let type = 'keyup';
|
const type = 'keyup';
|
||||||
let leftKeyOpts = {
|
const leftKeyOpts = {
|
||||||
element,
|
element,
|
||||||
type,
|
type,
|
||||||
key: 'ArrowLeft',
|
key: 'ArrowLeft',
|
||||||
keyCode: 37
|
keyCode: 37
|
||||||
};
|
};
|
||||||
let rightKeyOpts = {
|
const rightKeyOpts = {
|
||||||
element,
|
element,
|
||||||
type,
|
type,
|
||||||
key: 'ArrowRight',
|
key: 'ArrowRight',
|
||||||
keyCode: 39
|
keyCode: 39
|
||||||
};
|
};
|
||||||
|
|
||||||
// left thrice
|
// left thrice
|
||||||
simulateKeyEvent(leftKeyOpts);
|
simulateKeyEvent(leftKeyOpts);
|
||||||
simulateKeyEvent(leftKeyOpts);
|
simulateKeyEvent(leftKeyOpts);
|
||||||
simulateKeyEvent(leftKeyOpts);
|
simulateKeyEvent(leftKeyOpts);
|
||||||
// right once
|
// right once
|
||||||
simulateKeyEvent(rightKeyOpts);
|
simulateKeyEvent(rightKeyOpts);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
const imageInfo = getImageInfo(parent);
|
const imageInfo = getImageInfo(parent);
|
||||||
|
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it ('shows an auto scroll button when scroll to left', (done) => {
|
it ('shows an auto scroll button when scroll to left', (done) => {
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
&__content {
|
&__content {
|
||||||
$m: $interiorMargin;
|
$m: $interiorMargin;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
grid-column-gap: $m;
|
grid-column-gap: $m;
|
||||||
grid-row-gap: $m;
|
grid-row-gap: $m;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<div class="c-status-poll__section c-status-poll-panel__content c-spq">
|
<div class="c-status-poll__section c-status-poll-panel__content c-spq">
|
||||||
<!-- Grid layout -->
|
<!-- Grid layout -->
|
||||||
<div class="c-spq__label">Current:</div>
|
<div class="c-spq__label">Current poll:</div>
|
||||||
<div class="c-spq__value c-status-poll-panel__poll-question">{{ currentPollQuestion }}</div>
|
<div class="c-spq__value c-status-poll-panel__poll-question">{{ currentPollQuestion }}</div>
|
||||||
|
|
||||||
<template v-if="statusCountViewModel.length > 0">
|
<template v-if="statusCountViewModel.length > 0">
|
||||||
@ -43,6 +43,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="entry in statusCountViewModel"
|
v-for="entry in statusCountViewModel"
|
||||||
:key="entry.status.key"
|
:key="entry.status.key"
|
||||||
|
:title="entry.status.label"
|
||||||
class="c-status-poll-report__count"
|
class="c-status-poll-report__count"
|
||||||
:style="[{
|
:style="[{
|
||||||
background: entry.status.statusBgColor,
|
background: entry.status.statusBgColor,
|
||||||
@ -69,6 +70,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="c-button"
|
class="c-button"
|
||||||
|
title="Publish a new poll question and reset previous responses"
|
||||||
@click="updatePollQuestion"
|
@click="updatePollQuestion"
|
||||||
>Update</button>
|
>Update</button>
|
||||||
</div>
|
</div>
|
||||||
@ -78,6 +80,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inject: ['openmct', 'indicator', 'configuration'],
|
inject: ['openmct', 'indicator', 'configuration'],
|
||||||
@ -118,6 +121,9 @@ export default {
|
|||||||
this.openmct.user.status.off('statusChange', this.fetchStatusSummary);
|
this.openmct.user.status.off('statusChange', this.fetchStatusSummary);
|
||||||
this.openmct.user.status.off('pollQuestionChange', this.setPollQuestion);
|
this.openmct.user.status.off('pollQuestionChange', this.setPollQuestion);
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchStatusSummary = _.debounce(this.fetchStatusSummary);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchCurrentPoll() {
|
async fetchCurrentPoll() {
|
||||||
const pollQuestion = await this.openmct.user.status.getPollQuestion();
|
const pollQuestion = await this.openmct.user.status.getPollQuestion();
|
||||||
|
@ -45,8 +45,7 @@ export default function CouchDocument(id, model, rev, markDeleted) {
|
|||||||
"category": "domain object",
|
"category": "domain object",
|
||||||
"type": model.type,
|
"type": model.type,
|
||||||
"owner": "admin",
|
"owner": "admin",
|
||||||
"name": model.name,
|
"name": model.name
|
||||||
"created": Date.now()
|
|
||||||
},
|
},
|
||||||
"model": model
|
"model": model
|
||||||
};
|
};
|
||||||
|
@ -199,6 +199,11 @@ class CouchObjectProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let response = null;
|
let response = null;
|
||||||
|
|
||||||
|
if (!this.isObservingObjectChanges()) {
|
||||||
|
this.#observeObjectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = await fetch(this.url + '/' + subPath, fetchOptions);
|
response = await fetch(this.url + '/' + subPath, fetchOptions);
|
||||||
const { status } = response;
|
const { status } = response;
|
||||||
@ -210,9 +215,13 @@ class CouchObjectProvider {
|
|||||||
// Network error, CouchDB unreachable.
|
// Network error, CouchDB unreachable.
|
||||||
if (response === null) {
|
if (response === null) {
|
||||||
this.indicator.setIndicatorToState(DISCONNECTED);
|
this.indicator.setIndicatorToState(DISCONNECTED);
|
||||||
}
|
console.error(error.message);
|
||||||
|
throw new Error(`CouchDB Error - No response"`);
|
||||||
|
} else {
|
||||||
|
console.error(error.message);
|
||||||
|
|
||||||
console.error(error.message);
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,6 +383,8 @@ class CouchObjectProvider {
|
|||||||
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
|
return this.request(ALL_DOCS, 'POST', query, signal).then((response) => {
|
||||||
if (response && response.rows !== undefined) {
|
if (response && response.rows !== undefined) {
|
||||||
return response.rows.reduce((map, row) => {
|
return response.rows.reduce((map, row) => {
|
||||||
|
//row.doc === null if the document does not exist.
|
||||||
|
//row.doc === undefined if the document is not found.
|
||||||
if (row.doc !== undefined) {
|
if (row.doc !== undefined) {
|
||||||
map[row.key] = this.#getModel(row.doc);
|
map[row.key] = this.#getModel(row.doc);
|
||||||
}
|
}
|
||||||
@ -471,9 +482,6 @@ class CouchObjectProvider {
|
|||||||
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
this.observers[keyString] = this.observers[keyString].filter(observer => observer !== callback);
|
||||||
if (this.observers[keyString].length === 0) {
|
if (this.observers[keyString].length === 0) {
|
||||||
delete this.observers[keyString];
|
delete this.observers[keyString];
|
||||||
if (Object.keys(this.observers).length === 0 && this.isObservingObjectChanges()) {
|
|
||||||
this.stopObservingObjectChanges();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -498,7 +506,6 @@ class CouchObjectProvider {
|
|||||||
} else {
|
} else {
|
||||||
this.#initiateSharedWorkerFetchChanges(sseURL.toString());
|
this.#initiateSharedWorkerFetchChanges(sseURL.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -525,18 +532,24 @@ class CouchObjectProvider {
|
|||||||
|
|
||||||
onEventError(error) {
|
onEventError(error) {
|
||||||
console.error('Error on feed', error);
|
console.error('Error on feed', error);
|
||||||
if (Object.keys(this.observers).length > 0) {
|
const { readyState } = error.target;
|
||||||
this.#observeObjectChanges();
|
this.#updateIndicatorStatus(readyState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onEventOpen(event) {
|
||||||
|
const { readyState } = event.target;
|
||||||
|
this.#updateIndicatorStatus(readyState);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEventMessage(event) {
|
onEventMessage(event) {
|
||||||
|
const { readyState } = event.target;
|
||||||
const eventData = JSON.parse(event.data);
|
const eventData = JSON.parse(event.data);
|
||||||
const identifier = {
|
const identifier = {
|
||||||
namespace: this.namespace,
|
namespace: this.namespace,
|
||||||
key: eventData.id
|
key: eventData.id
|
||||||
};
|
};
|
||||||
const keyString = this.openmct.objects.makeKeyString(identifier);
|
const keyString = this.openmct.objects.makeKeyString(identifier);
|
||||||
|
this.#updateIndicatorStatus(readyState);
|
||||||
let observersForObject = this.observers[keyString];
|
let observersForObject = this.observers[keyString];
|
||||||
|
|
||||||
if (observersForObject) {
|
if (observersForObject) {
|
||||||
@ -559,17 +572,18 @@ class CouchObjectProvider {
|
|||||||
|
|
||||||
this.stopObservingObjectChanges = () => {
|
this.stopObservingObjectChanges = () => {
|
||||||
controller.abort();
|
controller.abort();
|
||||||
couchEventSource.removeEventListener('message', this.onEventMessage);
|
couchEventSource.removeEventListener('message', this.onEventMessage.bind(this));
|
||||||
delete this.stopObservingObjectChanges;
|
delete this.stopObservingObjectChanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug('⇿ Opening CouchDB change feed connection ⇿');
|
console.debug('⇿ Opening CouchDB change feed connection ⇿');
|
||||||
|
|
||||||
couchEventSource = new EventSource(url);
|
couchEventSource = new EventSource(url);
|
||||||
couchEventSource.onerror = this.onEventError;
|
couchEventSource.onerror = this.onEventError.bind(this);
|
||||||
|
couchEventSource.onopen = this.onEventOpen.bind(this);
|
||||||
|
|
||||||
// start listening for events
|
// start listening for events
|
||||||
couchEventSource.addEventListener('message', this.onEventMessage);
|
couchEventSource.addEventListener('message', this.onEventMessage.bind(this));
|
||||||
|
|
||||||
console.debug('⇿ Opened connection ⇿');
|
console.debug('⇿ Opened connection ⇿');
|
||||||
}
|
}
|
||||||
@ -587,6 +601,31 @@ class CouchObjectProvider {
|
|||||||
return intermediateResponse;
|
return intermediateResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the indicator status based on the readyState of the EventSource
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#updateIndicatorStatus(readyState) {
|
||||||
|
let message;
|
||||||
|
switch (readyState) {
|
||||||
|
case EventSource.CONNECTING:
|
||||||
|
message = 'pending';
|
||||||
|
break;
|
||||||
|
case EventSource.OPEN:
|
||||||
|
message = 'open';
|
||||||
|
break;
|
||||||
|
case EventSource.CLOSED:
|
||||||
|
message = 'close';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
message = 'unknown';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indicatorState = this.#messageToIndicatorState(message);
|
||||||
|
this.indicator.setIndicatorToState(indicatorState);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -614,8 +653,8 @@ class CouchObjectProvider {
|
|||||||
this.objectQueue[key].pending = true;
|
this.objectQueue[key].pending = true;
|
||||||
const queued = this.objectQueue[key].dequeue();
|
const queued = this.objectQueue[key].dequeue();
|
||||||
let document = new CouchDocument(key, queued.model);
|
let document = new CouchDocument(key, queued.model);
|
||||||
|
document.metadata.created = Date.now();
|
||||||
this.request(key, "PUT", document).then((response) => {
|
this.request(key, "PUT", document).then((response) => {
|
||||||
console.log('create check response', key);
|
|
||||||
this.#checkResponse(response, queued.intermediateResponse, key);
|
this.#checkResponse(response, queued.intermediateResponse, key);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
queued.intermediateResponse.reject(error);
|
queued.intermediateResponse.reject(error);
|
||||||
|
@ -152,7 +152,10 @@ describe('the plugin', () => {
|
|||||||
mockDomainObject.id = mockDomainObject.identifier.key;
|
mockDomainObject.id = mockDomainObject.identifier.key;
|
||||||
|
|
||||||
const fakeUpdateEvent = {
|
const fakeUpdateEvent = {
|
||||||
data: JSON.stringify(mockDomainObject)
|
data: JSON.stringify(mockDomainObject),
|
||||||
|
target: {
|
||||||
|
readyState: EventSource.CONNECTED
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line require-await
|
// eslint-disable-next-line require-await
|
||||||
@ -170,7 +173,6 @@ describe('the plugin', () => {
|
|||||||
expect(provider.create).toHaveBeenCalled();
|
expect(provider.create).toHaveBeenCalled();
|
||||||
expect(provider.observe).toHaveBeenCalled();
|
expect(provider.observe).toHaveBeenCalled();
|
||||||
expect(provider.isObservingObjectChanges).toHaveBeenCalled();
|
expect(provider.isObservingObjectChanges).toHaveBeenCalled();
|
||||||
expect(provider.isObservingObjectChanges.calls.mostRecent().returnValue).toBe(true);
|
|
||||||
|
|
||||||
//Set modified timestamp it detects a change and persists the updated model.
|
//Set modified timestamp it detects a change and persists the updated model.
|
||||||
mockDomainObject.modified = mockDomainObject.persisted + 1;
|
mockDomainObject.modified = mockDomainObject.persisted + 1;
|
||||||
@ -181,6 +183,7 @@ describe('the plugin', () => {
|
|||||||
expect(updatedResult).toBeTrue();
|
expect(updatedResult).toBeTrue();
|
||||||
expect(provider.update).toHaveBeenCalled();
|
expect(provider.update).toHaveBeenCalled();
|
||||||
expect(provider.fetchChanges).toHaveBeenCalled();
|
expect(provider.fetchChanges).toHaveBeenCalled();
|
||||||
|
expect(provider.isObservingObjectChanges.calls.mostRecent().returnValue).toBe(true);
|
||||||
sharedWorkerCallback(fakeUpdateEvent);
|
sharedWorkerCallback(fakeUpdateEvent);
|
||||||
expect(provider.onEventMessage).toHaveBeenCalled();
|
expect(provider.onEventMessage).toHaveBeenCalled();
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ const exportPNG = {
|
|||||||
name: 'Export as PNG',
|
name: 'Export as PNG',
|
||||||
key: 'export-as-png',
|
key: 'export-as-png',
|
||||||
description: 'Export This View\'s Data as PNG',
|
description: 'Export This View\'s Data as PNG',
|
||||||
cssClass: 'c-icon-button icon-download',
|
cssClass: 'icon-download',
|
||||||
group: 'view',
|
group: 'view',
|
||||||
invoke(objectPath, view) {
|
invoke(objectPath, view) {
|
||||||
view.getViewContext().exportPNG();
|
view.getViewContext().exportPNG();
|
||||||
@ -36,7 +36,7 @@ const exportJPG = {
|
|||||||
name: 'Export as JPG',
|
name: 'Export as JPG',
|
||||||
key: 'export-as-jpg',
|
key: 'export-as-jpg',
|
||||||
description: 'Export This View\'s Data as JPG',
|
description: 'Export This View\'s Data as JPG',
|
||||||
cssClass: 'c-icon-button icon-download',
|
cssClass: 'icon-download',
|
||||||
group: 'view',
|
group: 'view',
|
||||||
invoke(objectPath, view) {
|
invoke(objectPath, view) {
|
||||||
view.getViewContext().exportJPG();
|
view.getViewContext().exportJPG();
|
||||||
|
@ -34,6 +34,12 @@ export default class Model extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super();
|
super();
|
||||||
|
Object.defineProperty(this, '_events', {
|
||||||
|
value: this._events,
|
||||||
|
enumerable: false,
|
||||||
|
configurable: false,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
|
||||||
//need to do this as we're already extending EventEmitter
|
//need to do this as we're already extending EventEmitter
|
||||||
eventHelpers.extend(this);
|
eventHelpers.extend(this);
|
||||||
|
@ -27,6 +27,7 @@ import OverlayPlotCompositionPolicy from './overlayPlot/OverlayPlotCompositionPo
|
|||||||
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
import StackedPlotCompositionPolicy from './stackedPlot/StackedPlotCompositionPolicy';
|
||||||
import PlotViewActions from "./actions/ViewActions";
|
import PlotViewActions from "./actions/ViewActions";
|
||||||
import StackedPlotsInspectorViewProvider from "./inspector/StackedPlotsInspectorViewProvider";
|
import StackedPlotsInspectorViewProvider from "./inspector/StackedPlotsInspectorViewProvider";
|
||||||
|
import stackedPlotConfigurationInterceptor from "./stackedPlot/stackedPlotConfigurationInterceptor";
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return function install(openmct) {
|
return function install(openmct) {
|
||||||
@ -64,6 +65,8 @@ export default function () {
|
|||||||
priority: 890
|
priority: 890
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stackedPlotConfigurationInterceptor(openmct);
|
||||||
|
|
||||||
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new StackedPlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new OverlayPlotViewProvider(openmct));
|
||||||
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
openmct.objectViews.addProvider(new PlotViewProvider(openmct));
|
||||||
|
@ -20,11 +20,19 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
// this will be called from the test suite with
|
export default function stackedPlotConfigurationInterceptor(openmct) {
|
||||||
// await page.addInitScript({ path: path.join(__dirname, 'addInitGauge.js') });
|
|
||||||
// it will install the Gauge since it is not installed by default
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
openmct.objects.addGetInterceptor({
|
||||||
const openmct = window.openmct;
|
appliesTo: (identifier, domainObject) => {
|
||||||
openmct.install(openmct.plugins.Gauge());
|
return domainObject && domainObject.type === 'telemetry.plot.stacked';
|
||||||
});
|
},
|
||||||
|
invoke: (identifier, object) => {
|
||||||
|
|
||||||
|
if (object && object.configuration && object.configuration.series === undefined) {
|
||||||
|
object.configuration.series = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -20,6 +20,7 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
import DefaultClock from '../../utils/clock/DefaultClock';
|
import DefaultClock from '../../utils/clock/DefaultClock';
|
||||||
|
import remoteClockRequestInterceptor from './requestInterceptor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link openmct.TimeAPI.Clock} that updates the temporal bounds of the
|
* A {@link openmct.TimeAPI.Clock} that updates the temporal bounds of the
|
||||||
@ -49,6 +50,14 @@ export default class RemoteClock extends DefaultClock {
|
|||||||
|
|
||||||
this.lastTick = 0;
|
this.lastTick = 0;
|
||||||
|
|
||||||
|
this.openmct.telemetry.addRequestInterceptor(
|
||||||
|
remoteClockRequestInterceptor(
|
||||||
|
this.openmct,
|
||||||
|
this.identifier,
|
||||||
|
this.#waitForReady.bind(this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
this._processDatum = this._processDatum.bind(this);
|
this._processDatum = this._processDatum.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,4 +138,25 @@ export default class RemoteClock extends DefaultClock {
|
|||||||
return timeFormatter.parse(datum);
|
return timeFormatter.parse(datum);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the clock to have a non-default tick value.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#waitForReady() {
|
||||||
|
const waitForInitialTick = (resolve) => {
|
||||||
|
if (this.lastTick > 0) {
|
||||||
|
const offsets = this.openmct.time.clockOffsets();
|
||||||
|
resolve({
|
||||||
|
start: this.lastTick + offsets.start,
|
||||||
|
end: this.lastTick + offsets.end
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(() => waitForInitialTick(resolve), 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(waitForInitialTick);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
46
src/plugins/remoteClock/requestInterceptor.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2022, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
function remoteClockRequestInterceptor(openmct, remoteClockIdentifier, waitForBounds) {
|
||||||
|
let remoteClockLoaded = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
appliesTo: () => {
|
||||||
|
// Get the activeClock from the Global Time Context
|
||||||
|
const { activeClock } = openmct.time.getContextForView();
|
||||||
|
|
||||||
|
return activeClock !== undefined
|
||||||
|
&& activeClock.key === 'remote-clock'
|
||||||
|
&& !remoteClockLoaded;
|
||||||
|
},
|
||||||
|
invoke: async (request) => {
|
||||||
|
const { start, end } = await waitForBounds();
|
||||||
|
remoteClockLoaded = true;
|
||||||
|
request.start = start;
|
||||||
|
request.end = end;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default remoteClockRequestInterceptor;
|
@ -78,7 +78,7 @@ class StaticModelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseTreeLeaf(leafKey, leafValue, idMap, namespace) {
|
parseTreeLeaf(leafKey, leafValue, idMap, namespace) {
|
||||||
if (!leafValue) {
|
if (leafValue === null || leafValue === undefined) {
|
||||||
return leafValue;
|
return leafValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,9 +225,7 @@ define(
|
|||||||
sortBy(sortOptions) {
|
sortBy(sortOptions) {
|
||||||
if (arguments.length > 0) {
|
if (arguments.length > 0) {
|
||||||
this.sortOptions = sortOptions;
|
this.sortOptions = sortOptions;
|
||||||
performance.mark('table:row:sort:start');
|
|
||||||
this.rows = _.orderBy(this.rows, (row) => row.getParsedValue(sortOptions.key), sortOptions.direction);
|
this.rows = _.orderBy(this.rows, (row) => row.getParsedValue(sortOptions.key), sortOptions.direction);
|
||||||
performance.mark('table:row:sort:stop');
|
|
||||||
this.emit('sort');
|
this.emit('sort');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,7 +612,6 @@ export default {
|
|||||||
this.calculateScrollbarWidth();
|
this.calculateScrollbarWidth();
|
||||||
},
|
},
|
||||||
sortBy(columnKey) {
|
sortBy(columnKey) {
|
||||||
performance.mark('table:sort');
|
|
||||||
// If sorting by the same column, flip the sort direction.
|
// If sorting by the same column, flip the sort direction.
|
||||||
if (this.sortOptions.key === columnKey) {
|
if (this.sortOptions.key === columnKey) {
|
||||||
if (this.sortOptions.direction === 'asc') {
|
if (this.sortOptions.direction === 'asc') {
|
||||||
@ -669,7 +668,6 @@ export default {
|
|||||||
this.setHeight();
|
this.setHeight();
|
||||||
},
|
},
|
||||||
rowsAdded(rows) {
|
rowsAdded(rows) {
|
||||||
performance.mark('row:added');
|
|
||||||
this.setHeight();
|
this.setHeight();
|
||||||
|
|
||||||
let sizingRow;
|
let sizingRow;
|
||||||
@ -691,7 +689,6 @@ export default {
|
|||||||
this.updateVisibleRows();
|
this.updateVisibleRows();
|
||||||
},
|
},
|
||||||
rowsRemoved(rows) {
|
rowsRemoved(rows) {
|
||||||
performance.mark('row:removed');
|
|
||||||
this.setHeight();
|
this.setHeight();
|
||||||
this.updateVisibleRows();
|
this.updateVisibleRows();
|
||||||
},
|
},
|
||||||
|
@ -135,7 +135,7 @@ describe("the plugin", () => {
|
|||||||
let tableInstance;
|
let tableInstance;
|
||||||
let mockClock;
|
let mockClock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
openmct.time.timeSystem('utc', {
|
openmct.time.timeSystem('utc', {
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 4
|
end: 4
|
||||||
@ -210,16 +210,8 @@ describe("the plugin", () => {
|
|||||||
'some-other-key': 'some-other-value 3'
|
'some-other-key': 'some-other-value 3'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
let telemetryPromiseResolve;
|
|
||||||
let telemetryPromise = new Promise((resolve) => {
|
|
||||||
telemetryPromiseResolve = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
historicalProvider.request = () => {
|
historicalProvider.request = () => Promise.resolve(testTelemetry);
|
||||||
telemetryPromiseResolve(testTelemetry);
|
|
||||||
|
|
||||||
return telemetryPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
openmct.router.path = [testTelemetryObject];
|
openmct.router.path = [testTelemetryObject];
|
||||||
|
|
||||||
@ -230,7 +222,7 @@ describe("the plugin", () => {
|
|||||||
|
|
||||||
tableInstance = tableView.getTable();
|
tableInstance = tableView.getTable();
|
||||||
|
|
||||||
return telemetryPromise.then(() => Vue.nextTick());
|
await Vue.nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -255,13 +247,10 @@ describe("the plugin", () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders a row for every telemetry datum returned", (done) => {
|
it("Renders a row for every telemetry datum returned", async () => {
|
||||||
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
Vue.nextTick(() => {
|
await Vue.nextTick();
|
||||||
expect(rows.length).toBe(3);
|
expect(rows.length).toBe(3);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Renders a column for every item in telemetry metadata", () => {
|
it("Renders a column for every item in telemetry metadata", () => {
|
||||||
@ -273,7 +262,7 @@ describe("the plugin", () => {
|
|||||||
expect(headers[3].innerText).toBe('Another attribute');
|
expect(headers[3].innerText).toBe('Another attribute');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Supports column reordering via drag and drop", () => {
|
it("Supports column reordering via drag and drop", async () => {
|
||||||
let columns = element.querySelectorAll('tr.c-telemetry-table__headers__labels th');
|
let columns = element.querySelectorAll('tr.c-telemetry-table__headers__labels th');
|
||||||
let fromColumn = columns[0];
|
let fromColumn = columns[0];
|
||||||
let toColumn = columns[1];
|
let toColumn = columns[1];
|
||||||
@ -292,54 +281,43 @@ describe("the plugin", () => {
|
|||||||
toColumn.dispatchEvent(dragOverEvent);
|
toColumn.dispatchEvent(dragOverEvent);
|
||||||
toColumn.dispatchEvent(dropEvent);
|
toColumn.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
await Vue.nextTick();
|
||||||
columns = element.querySelectorAll('tr.c-telemetry-table__headers__labels th');
|
columns = element.querySelectorAll('tr.c-telemetry-table__headers__labels th');
|
||||||
let firstColumn = columns[0];
|
let firstColumn = columns[0];
|
||||||
let secondColumn = columns[1];
|
let secondColumn = columns[1];
|
||||||
let firstColumnText = firstColumn.querySelector('span.c-telemetry-table__headers__label').innerText;
|
let firstColumnText = firstColumn.querySelector('span.c-telemetry-table__headers__label').innerText;
|
||||||
let secondColumnText = secondColumn.querySelector('span.c-telemetry-table__headers__label').innerText;
|
let secondColumnText = secondColumn.querySelector('span.c-telemetry-table__headers__label').innerText;
|
||||||
|
expect(fromColumnText).not.toEqual(firstColumnText);
|
||||||
expect(fromColumnText).not.toEqual(firstColumnText);
|
expect(fromColumnText).toEqual(secondColumnText);
|
||||||
expect(fromColumnText).toEqual(secondColumnText);
|
expect(toColumnText).not.toEqual(secondColumnText);
|
||||||
expect(toColumnText).not.toEqual(secondColumnText);
|
expect(toColumnText).toEqual(firstColumnText);
|
||||||
expect(toColumnText).toEqual(firstColumnText);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Supports filtering telemetry by regular text search", () => {
|
it("Supports filtering telemetry by regular text search", async () => {
|
||||||
tableInstance.tableRows.setColumnFilter("some-key", "1");
|
tableInstance.tableRows.setColumnFilter("some-key", "1");
|
||||||
|
await Vue.nextTick();
|
||||||
|
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
expect(filteredRowElements.length).toEqual(1);
|
||||||
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
tableInstance.tableRows.setColumnFilter("some-key", "");
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
expect(filteredRowElements.length).toEqual(1);
|
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
expect(allRowElements.length).toEqual(3);
|
||||||
tableInstance.tableRows.setColumnFilter("some-key", "");
|
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
|
||||||
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
|
||||||
|
|
||||||
expect(allRowElements.length).toEqual(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Supports filtering using Regex", () => {
|
it("Supports filtering using Regex", async () => {
|
||||||
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value$");
|
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value$");
|
||||||
|
await Vue.nextTick();
|
||||||
|
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
expect(filteredRowElements.length).toEqual(0);
|
||||||
let filteredRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
|
||||||
|
|
||||||
expect(filteredRowElements.length).toEqual(0);
|
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value");
|
||||||
|
await Vue.nextTick();
|
||||||
|
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
||||||
|
|
||||||
tableInstance.tableRows.setColumnRegexFilter("some-key", "^some-value");
|
expect(allRowElements.length).toEqual(3);
|
||||||
|
|
||||||
return Vue.nextTick().then(() => {
|
|
||||||
let allRowElements = element.querySelectorAll('table.c-telemetry-table__body tr');
|
|
||||||
|
|
||||||
expect(allRowElements.length).toEqual(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("displays the correct number of column headers when the configuration is mutated", async () => {
|
it("displays the correct number of column headers when the configuration is mutated", async () => {
|
||||||
@ -402,7 +380,7 @@ describe("the plugin", () => {
|
|||||||
expect(element.querySelector('div.c-table.is-paused')).not.toBeNull();
|
expect(element.querySelector('div.c-table.is-paused')).not.toBeNull();
|
||||||
|
|
||||||
const currentBounds = openmct.time.bounds();
|
const currentBounds = openmct.time.bounds();
|
||||||
|
await Vue.nextTick();
|
||||||
const newBounds = {
|
const newBounds = {
|
||||||
start: currentBounds.start,
|
start: currentBounds.start,
|
||||||
end: currentBounds.end - 3
|
end: currentBounds.end - 3
|
||||||
@ -410,17 +388,10 @@ describe("the plugin", () => {
|
|||||||
|
|
||||||
// Manually change the time bounds
|
// Manually change the time bounds
|
||||||
openmct.time.bounds(newBounds);
|
openmct.time.bounds(newBounds);
|
||||||
|
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
|
|
||||||
// Verify table is no longer paused
|
// Verify table is no longer paused
|
||||||
expect(element.querySelector('div.c-table.is-paused')).toBeNull();
|
expect(element.querySelector('div.c-table.is-paused')).toBeNull();
|
||||||
|
|
||||||
await Vue.nextTick();
|
|
||||||
|
|
||||||
// Verify table displays the correct number of rows within the new bounds
|
|
||||||
const tableRows = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr');
|
|
||||||
expect(tableRows.length).toEqual(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Unpauses the table on user bounds change if paused by button", async () => {
|
it("Unpauses the table on user bounds change if paused by button", async () => {
|
||||||
@ -428,19 +399,18 @@ describe("the plugin", () => {
|
|||||||
|
|
||||||
// Pause by button
|
// Pause by button
|
||||||
viewContext.togglePauseByButton();
|
viewContext.togglePauseByButton();
|
||||||
|
|
||||||
await Vue.nextTick();
|
await Vue.nextTick();
|
||||||
|
|
||||||
// Verify table is paused
|
// Verify table is paused
|
||||||
expect(element.querySelector('div.c-table.is-paused')).not.toBeNull();
|
expect(element.querySelector('div.c-table.is-paused')).not.toBeNull();
|
||||||
|
|
||||||
const currentBounds = openmct.time.bounds();
|
const currentBounds = openmct.time.bounds();
|
||||||
|
await Vue.nextTick();
|
||||||
|
|
||||||
const newBounds = {
|
const newBounds = {
|
||||||
start: currentBounds.start,
|
start: currentBounds.start,
|
||||||
end: currentBounds.end - 3
|
end: currentBounds.end - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// Manually change the time bounds
|
// Manually change the time bounds
|
||||||
openmct.time.bounds(newBounds);
|
openmct.time.bounds(newBounds);
|
||||||
|
|
||||||
@ -448,12 +418,6 @@ describe("the plugin", () => {
|
|||||||
|
|
||||||
// Verify table is no longer paused
|
// Verify table is no longer paused
|
||||||
expect(element.querySelector('div.c-table.is-paused')).toBeNull();
|
expect(element.querySelector('div.c-table.is-paused')).toBeNull();
|
||||||
|
|
||||||
await Vue.nextTick();
|
|
||||||
|
|
||||||
// Verify table displays the correct number of rows within the new bounds
|
|
||||||
const tableRows = element.querySelectorAll('table.c-telemetry-table__body > tbody > tr');
|
|
||||||
expect(tableRows.length).toEqual(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Does not unpause the table on tick", async () => {
|
it("Does not unpause the table on tick", async () => {
|
||||||
|
@ -223,15 +223,16 @@ export default {
|
|||||||
},
|
},
|
||||||
resizeSoView() {
|
resizeSoView() {
|
||||||
let cW = this.$refs.soView.offsetWidth;
|
let cW = this.$refs.soView.offsetWidth;
|
||||||
|
let widths = [220, 600];
|
||||||
let wClass = '';
|
let wClass = '';
|
||||||
|
|
||||||
if (cW < 220) {
|
for (let width of widths) {
|
||||||
wClass = CSS_WIDTH_LESS_STR + '220';
|
if (cW < width) {
|
||||||
} else if (cW < 600) {
|
wClass = wClass.concat(' ', CSS_WIDTH_LESS_STR, width);
|
||||||
wClass = CSS_WIDTH_LESS_STR + '600';
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.widthClass = wClass;
|
this.widthClass = wClass.trimStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
margin-bottom: $interiorMarginSm;
|
margin-bottom: $interiorMarginSm;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
|
@include smallerControlButtons; // Make button in frame headers a bit smaller
|
||||||
|
|
||||||
.c-object-label {
|
.c-object-label {
|
||||||
font-size: 1.05em;
|
font-size: 1.05em;
|
||||||
@ -132,8 +133,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallerControlButtons;
|
|
||||||
|
|
||||||
&.has-complex-content {
|
&.has-complex-content {
|
||||||
> .c-so-view__view-large { display: block; }
|
> .c-so-view__view-large { display: block; }
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// <a> tag and draggable element that holds type icon and name.
|
// <a> tag and draggable element that holds type icon and name.
|
||||||
// Used mostly in trees and lists
|
// Used mostly in trees and lists
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline; // Provides better vertical alignment than center
|
align-items: center;
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@ -107,7 +107,12 @@ export default {
|
|||||||
this.preview();
|
this.preview();
|
||||||
} else {
|
} else {
|
||||||
const objectPath = this.result.originalPath;
|
const objectPath = this.result.originalPath;
|
||||||
const resultUrl = objectPathToUrl(this.openmct, objectPath);
|
let resultUrl = objectPathToUrl(this.openmct, objectPath);
|
||||||
|
// get rid of ROOT if extant
|
||||||
|
if (resultUrl.includes('/ROOT')) {
|
||||||
|
resultUrl = resultUrl.split('/ROOT').join('');
|
||||||
|
}
|
||||||
|
|
||||||
this.openmct.router.navigate(resultUrl);
|
this.openmct.router.navigate(resultUrl);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -50,6 +50,10 @@ class ApplicationRouter extends EventEmitter {
|
|||||||
this.started = false;
|
this.started = false;
|
||||||
|
|
||||||
this.setHash = _.debounce(this.setHash.bind(this), 300);
|
this.setHash = _.debounce(this.setHash.bind(this), 300);
|
||||||
|
|
||||||
|
openmct.once('destroy', () => {
|
||||||
|
this.destroy();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public Methods
|
// Public Methods
|
||||||
|
@ -7,12 +7,19 @@ const webpack = require('webpack');
|
|||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
|
||||||
const {VueLoaderPlugin} = require('vue-loader');
|
const {VueLoaderPlugin} = require('vue-loader');
|
||||||
const gitRevision = require('child_process')
|
let gitRevision = 'error-retrieving-revision';
|
||||||
.execSync('git rev-parse HEAD')
|
let gitBranch = 'error-retrieving-branch';
|
||||||
.toString().trim();
|
|
||||||
const gitBranch = require('child_process')
|
try {
|
||||||
.execSync('git rev-parse --abbrev-ref HEAD')
|
gitRevision = require('child_process')
|
||||||
.toString().trim();
|
.execSync('git rev-parse HEAD')
|
||||||
|
.toString().trim();
|
||||||
|
gitBranch = require('child_process')
|
||||||
|
.execSync('git rev-parse --abbrev-ref HEAD')
|
||||||
|
.toString().trim();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {import('webpack').Configuration} */
|
/** @type {import('webpack').Configuration} */
|
||||||
const config = {
|
const config = {
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
// instrumentation using babel-plugin-istanbul (see babel.coverage.js)
|
// instrumentation using babel-plugin-istanbul (see babel.coverage.js)
|
||||||
|
|
||||||
const config = require('./webpack.dev');
|
const config = require('./webpack.dev');
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
config.devtool = false;
|
|
||||||
|
|
||||||
const vueLoaderRule = config.module.rules.find(r => r.use === 'vue-loader');
|
const vueLoaderRule = config.module.rules.find(r => r.use === 'vue-loader');
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
const CI = process.env.CI === 'true';
|
||||||
|
|
||||||
|
config.devtool = CI ? false : undefined;
|
||||||
|
|
||||||
vueLoaderRule.use = {
|
vueLoaderRule.use = {
|
||||||
loader: 'vue-loader'
|
loader: 'vue-loader'
|
||||||
|