Compare commits
12 Commits
v4.0.0-rc2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a06b51c5a | ||
|
|
ef8b353d01 | ||
|
|
6c5b925454 | ||
|
|
e91aba2e37 | ||
|
|
b18aa48141 | ||
|
|
d33da65dae | ||
|
|
e3adeb6a75 | ||
|
|
de3dad02b5 | ||
|
|
311ad0b87a | ||
|
|
f98eb31956 | ||
|
|
1671a585fb | ||
|
|
a3fb84ad43 |
@@ -22,9 +22,3 @@
|
||||
!index.html
|
||||
!openmct.js
|
||||
!SECURITY.md
|
||||
|
||||
# Add e2e tests to npm package
|
||||
!/e2e/**/*
|
||||
|
||||
# ... except our test-data folder files.
|
||||
/e2e/test-data/*.json
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
This is the OpenMCT common webpack file. It is imported by the other three webpack configurations:
|
||||
- webpack.prod.js - the production configuration for OpenMCT (default)
|
||||
- webpack.dev.js - the development configuration for OpenMCT
|
||||
- webpack.coverage.js - imports webpack.dev.js and adds code coverage
|
||||
- webpack.prod.mjs - the production configuration for OpenMCT (default)
|
||||
- webpack.dev.mjs - the development configuration for OpenMCT
|
||||
- webpack.coverage.mjs - imports webpack.dev.js and adds code coverage
|
||||
There are separate npm scripts to use these configurations, though simply running `npm install`
|
||||
will use the default production configuration.
|
||||
*/
|
||||
@@ -15,6 +15,7 @@ import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import { VueLoaderPlugin } from 'vue-loader';
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
let gitRevision = 'error-retrieving-revision';
|
||||
let gitBranch = 'error-retrieving-branch';
|
||||
|
||||
@@ -54,9 +55,11 @@ const config = {
|
||||
globalObject: 'this',
|
||||
filename: '[name].js',
|
||||
path: path.resolve(projectRootDir, 'dist'),
|
||||
library: 'openmct',
|
||||
libraryExport: 'default',
|
||||
libraryTarget: 'umd',
|
||||
library: {
|
||||
name: 'openmct',
|
||||
type: 'umd',
|
||||
export: 'default'
|
||||
},
|
||||
publicPath: '',
|
||||
hashFunction: 'xxhash64',
|
||||
clean: true
|
||||
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
This file extends the webpack.dev.js config to add babel istanbul coverage.
|
||||
This file extends the webpack.dev.mjs config to add babel istanbul coverage.
|
||||
OpenMCT Continuous Integration servers use this configuration to add code coverage
|
||||
information to pull requests.
|
||||
*/
|
||||
|
||||
import config from './webpack.dev.js';
|
||||
import config from './webpack.dev.mjs';
|
||||
|
||||
config.devtool = 'source-map';
|
||||
config.devServer.hot = false;
|
||||
@@ -16,7 +16,6 @@ config.module.rules.push({
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
retainLines: true,
|
||||
// eslint-disable-next-line no-undef
|
||||
plugins: [
|
||||
[
|
||||
'babel-plugin-istanbul',
|
||||
@@ -1,14 +1,15 @@
|
||||
/*
|
||||
This configuration should be used for development purposes. It contains full source map, a
|
||||
devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution.
|
||||
If OpenMCT is to be used for a production server, use webpack.prod.js instead.
|
||||
If OpenMCT is to be used for a production server, use webpack.prod.mjs instead.
|
||||
*/
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import path from 'path';
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import common from './webpack.common.js';
|
||||
import common from './webpack.common.mjs';
|
||||
|
||||
export default merge(common, {
|
||||
mode: 'development',
|
||||
@@ -6,7 +6,7 @@ It is the default webpack configuration.
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import common from './webpack.common.js';
|
||||
import common from './webpack.common.mjs';
|
||||
|
||||
export default merge(common, {
|
||||
mode: 'production',
|
||||
@@ -63,7 +63,7 @@ Once the file is generated, it can be published to codecov with
|
||||
### e2e
|
||||
The e2e line coverage is a bit more complex than the karma implementation. This is the general sequence of events:
|
||||
|
||||
1. Each e2e suite will start webpack with the ```npm run start:coverage``` command with config `webpack.coverage.js` and the `babel-plugin-istanbul` plugin to generate code coverage during e2e test execution using our custom [baseFixture](./baseFixtures.js).
|
||||
1. Each e2e suite will start webpack with the ```npm run start:coverage``` command with config `webpack.coverage.mjs` and the `babel-plugin-istanbul` plugin to generate code coverage during e2e test execution using our custom [baseFixture](./baseFixtures.js).
|
||||
1. During testcase execution, each e2e shard will generate its piece of the larger coverage suite. **This coverage file is not merged**. The raw coverage file is stored in a `.nyc_report` directory.
|
||||
1. [nyc](https://github.com/istanbuljs/nyc) converts this directory into a `lcov` file with the following command `npm run cov:e2e:report`
|
||||
1. Most of the tests are run in the '@stable' configuration and focus on chrome/ubuntu at a single resolution. This coverage is published to codecov with `npm run cov:e2e:stable:publish`.
|
||||
|
||||
7
e2e/.npmignore
Normal file
7
e2e/.npmignore
Normal file
@@ -0,0 +1,7 @@
|
||||
*
|
||||
!appActions.js
|
||||
!baseFixtures.js
|
||||
!pluginFixtures.js
|
||||
!avpFixtures.js
|
||||
!index.js
|
||||
!*.md
|
||||
@@ -167,9 +167,9 @@ When an a11y test fails, the result must be interpreted in the html test report
|
||||
|
||||
The open source performance tests function in three ways which match their naming and folder structure:
|
||||
|
||||
`./e2e/tests/performance` - The tests at the root of this folder path detect functional changes which are mostly apparent with large performance regressions like [this](https://github.com/nasa/openmct/issues/6879). These tests run against openmct webpack in `production-mode` with the `npm run test:perf:localhost` script.
|
||||
`./e2e/tests/performance/contract/` - These tests serve as [contracts](https://martinfowler.com/bliki/ContractTest.html) for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. These tests run against openmct webpack in `dev-mode` with the `npm run test:perf:contract` script.
|
||||
`./e2e/tests/performance/memory/` - These tests execute memory leak detection checks in various ways. This is expected to evolve as we move to the `memlab` project. These tests run against openmct webpack in `production-mode` with the `npm run test:perf:memory` script.
|
||||
`tests/performance` - The tests at the root of this folder path detect functional changes which are mostly apparent with large performance regressions like [this](https://github.com/nasa/openmct/issues/6879). These tests run against openmct webpack in `production-mode` with the `npm run test:perf:localhost` script.
|
||||
`tests/performance/contract/` - These tests serve as [contracts](https://martinfowler.com/bliki/ContractTest.html) for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. These tests run against openmct webpack in `dev-mode` with the `npm run test:perf:contract` script.
|
||||
`tests/performance/memory/` - These tests execute memory leak detection checks in various ways. This is expected to evolve as we move to the `memlab` project. These tests run against openmct webpack in `production-mode` with the `npm run test:perf:memory` script.
|
||||
|
||||
These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright.
|
||||
|
||||
|
||||
@@ -60,14 +60,16 @@ function waitForAnimations(locator) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is part of our codecoverage shim.
|
||||
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Example Project}
|
||||
* @constant {string}
|
||||
*/
|
||||
const istanbulCLIOutput = path.join(process.cwd(), '.nyc_output');
|
||||
const istanbulCLIOutput = fileURLToPath(new URL('.nyc_output', import.meta.url));
|
||||
|
||||
const extendedTest = test.extend({
|
||||
/**
|
||||
* Path to output raw coverage files. Can be overridden in Playwright config file.
|
||||
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Example Project}
|
||||
* @constant {string}
|
||||
*/
|
||||
|
||||
coveragePath: [istanbulCLIOutput, { option: true }],
|
||||
/**
|
||||
* This allows the test to manipulate the browser clock. This is useful for Visual and Snapshot tests which need
|
||||
* the Time Indicator Clock to be in a specific state.
|
||||
@@ -148,17 +150,17 @@ const extendedTest = test.extend({
|
||||
* Extends the base context class to add codecoverage shim.
|
||||
* @see {@link https://github.com/mxschmitt/playwright-test-coverage Github Project}
|
||||
*/
|
||||
context: async ({ context }, use) => {
|
||||
context: async ({ context, coveragePath }, use) => {
|
||||
await context.addInitScript(() =>
|
||||
window.addEventListener('beforeunload', () =>
|
||||
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
|
||||
)
|
||||
);
|
||||
await fs.promises.mkdir(istanbulCLIOutput, { recursive: true });
|
||||
await fs.promises.mkdir(coveragePath, { recursive: true });
|
||||
await context.exposeFunction('collectIstanbulCoverage', (coverageJSON) => {
|
||||
if (coverageJSON) {
|
||||
fs.writeFileSync(
|
||||
path.join(istanbulCLIOutput, `playwright_coverage_${uuid()}.json`),
|
||||
path.join(coveragePath, `playwright_coverage_${uuid()}.json`),
|
||||
coverageJSON
|
||||
);
|
||||
}
|
||||
@@ -166,9 +168,9 @@ const extendedTest = test.extend({
|
||||
|
||||
await use(context);
|
||||
for (const page of context.pages()) {
|
||||
await page.evaluate(() =>
|
||||
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__))
|
||||
);
|
||||
await page.evaluate(() => {
|
||||
window.collectIstanbulCoverage(JSON.stringify(window.__coverage__));
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
47
e2e/helper/hotkeys/clipboard.js
Normal file
47
e2e/helper/hotkeys/clipboard.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
const modifier = isMac ? 'Meta' : 'Control';
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function selectAll(page) {
|
||||
await page.keyboard.press(`${modifier}+KeyA`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function copy(page) {
|
||||
await page.keyboard.press(`${modifier}+KeyC`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function paste(page) {
|
||||
await page.keyboard.press(`${modifier}+KeyV`);
|
||||
}
|
||||
|
||||
export { copy, paste, selectAll };
|
||||
23
e2e/helper/hotkeys/hotkeys.js
Normal file
23
e2e/helper/hotkeys/hotkeys.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2024, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
export * from './clipboard.js';
|
||||
@@ -28,16 +28,28 @@ import { fileURLToPath } from 'url';
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {string} text
|
||||
*/
|
||||
async function enterTextEntry(page, text) {
|
||||
// Click the 'Add Notebook Entry' area
|
||||
await page.locator(NOTEBOOK_DROP_AREA).click();
|
||||
|
||||
// enter text
|
||||
await page.getByLabel('Notebook Entry Input').last().fill(text);
|
||||
await addNotebookEntry(page);
|
||||
await enterTextInLastEntry(page, text);
|
||||
await commitEntry(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function addNotebookEntry(page) {
|
||||
await page.locator(NOTEBOOK_DROP_AREA).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function enterTextInLastEntry(page, text) {
|
||||
await page.getByLabel('Notebook Entry Input').last().fill(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
@@ -140,10 +152,13 @@ async function createNotebookEntryAndTags(page, iterations = 1) {
|
||||
}
|
||||
|
||||
export {
|
||||
addNotebookEntry,
|
||||
commitEntry,
|
||||
createNotebookAndEntry,
|
||||
createNotebookEntryAndTags,
|
||||
dragAndDropEmbed,
|
||||
enterTextEntry,
|
||||
enterTextInLastEntry,
|
||||
lockPage,
|
||||
startAndAddRestrictedNotebookObject
|
||||
};
|
||||
|
||||
8
e2e/index.js
Normal file
8
e2e/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Import everything from the specific fixture files
|
||||
import * as appActions from './appActions.js';
|
||||
import * as avpFixtures from './avpFixtures.js';
|
||||
import * as baseFixtures from './baseFixtures.js';
|
||||
import * as pluginFixtures from './pluginFixtures.js';
|
||||
|
||||
// Export these as named exports
|
||||
export { appActions, avpFixtures, baseFixtures, pluginFixtures };
|
||||
1449
e2e/package-lock.json
generated
Normal file
1449
e2e/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
e2e/package.json
Normal file
27
e2e/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "openmct-e2e",
|
||||
"version": "4.0.0-next",
|
||||
"description": "The Open MCT e2e framework",
|
||||
"type": "module",
|
||||
"module": "index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"pretest:visual": "npm install",
|
||||
"test": "npx playwright test",
|
||||
"test:visual": "percy exec"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sinonjs__fake-timers": "8.1.5",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@axe-core/playwright": "4.8.5",
|
||||
"sinon": "17.0.0"
|
||||
},
|
||||
"author": "NASA Ames Research Center",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { devices } from '@playwright/test';
|
||||
import { fileURLToPath } from 'url';
|
||||
const MAX_FAILURES = 5;
|
||||
const NUM_WORKERS = 2;
|
||||
|
||||
@@ -15,6 +16,7 @@ const config = {
|
||||
timeout: 60 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start:coverage',
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||
@@ -27,7 +29,9 @@ const config = {
|
||||
ignoreHTTPSErrors: true,
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
video: 'off'
|
||||
video: 'off',
|
||||
// @ts-ignore - custom configuration option for nyc codecoverage output path
|
||||
coveragePath: fileURLToPath(new URL('../.nyc_output', import.meta.url))
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 0,
|
||||
@@ -10,6 +10,7 @@ const config = {
|
||||
timeout: 30 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start:coverage',
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: true
|
||||
|
||||
@@ -14,6 +14,7 @@ const config = {
|
||||
timeout: 30 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start:coverage',
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||
@@ -27,7 +28,9 @@ const config = {
|
||||
ignoreHTTPSErrors: true,
|
||||
screenshot: 'only-on-failure',
|
||||
trace: 'on-first-retry',
|
||||
video: 'off'
|
||||
video: 'off',
|
||||
// @ts-ignore - custom configuration option for nyc codecoverage output path
|
||||
coveragePath: fileURLToPath(new URL('../.nyc_output', import.meta.url))
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 1, //Only for debugging purposes for trace: 'on-first-retry'
|
||||
@@ -10,6 +10,7 @@ const config = {
|
||||
workers: 1, //Only run in serial with 1 worker
|
||||
webServer: {
|
||||
command: 'npm run start', //need development mode for performance.marks and others
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
retries: 0, //Only for debugging purposes for trace: 'on-first-retry'
|
||||
@@ -10,6 +10,7 @@ const config = {
|
||||
workers: 1, //Only run in serial with 1 worker
|
||||
webServer: {
|
||||
command: 'npm run start:prod', //Production mode
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: false //Must be run with this option to prevent dev mode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig<{ theme: string }>} */
|
||||
const config = {
|
||||
retries: 0, // Visual tests should never retry due to snapshot comparison errors. Leaving as a shim
|
||||
@@ -10,6 +10,7 @@ const config = {
|
||||
workers: 1, //Lower stress on Circle CI Agent for Visual tests https://github.com/percy/cli/discussions/1067
|
||||
webServer: {
|
||||
command: 'npm run start:coverage',
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
|
||||
import { devices } from '@playwright/test';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
@@ -11,6 +10,7 @@ const config = {
|
||||
timeout: 60 * 1000,
|
||||
webServer: {
|
||||
command: 'npm run start', //Start in dev mode for hot reloading
|
||||
cwd: fileURLToPath(new URL('../', import.meta.url)), // Provide cwd for the root of the project
|
||||
url: 'http://localhost:8080/#',
|
||||
timeout: 200 * 1000,
|
||||
reuseExistingServer: true //This was originally disabled to prevent differences in local debugging vs. CI. However, it significantly speeds up local debugging.
|
||||
|
||||
@@ -31,8 +31,8 @@ import { createDomainObjectWithDefaults } from '../../appActions.js';
|
||||
import { expect, test } from '../../pluginFixtures.js';
|
||||
|
||||
const TEST_FOLDER = 'test folder';
|
||||
const jsonFilePath = 'e2e/test-data/ExampleLayouts.json';
|
||||
const imageFilePath = 'e2e/test-data/rick.jpg';
|
||||
const jsonFilePath = 'test-data/ExampleLayouts.json';
|
||||
const imageFilePath = 'test-data/rick.jpg';
|
||||
|
||||
test.describe('Form Validation Behavior', () => {
|
||||
test('Required Field indicators appear if title is empty and can be corrected', async ({
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
*****************************************************************************/
|
||||
import fs from 'fs';
|
||||
|
||||
import { getPreciseDuration } from '../../../../src/utils/duration.js';
|
||||
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../../appActions.js';
|
||||
import {
|
||||
assertPlanActivities,
|
||||
@@ -132,3 +131,58 @@ test.describe('Gantt Chart', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const ONE_SECOND = 1000;
|
||||
const ONE_MINUTE = 60 * ONE_SECOND;
|
||||
const ONE_HOUR = ONE_MINUTE * 60;
|
||||
const ONE_DAY = ONE_HOUR * 24;
|
||||
|
||||
function normalizeAge(num) {
|
||||
const hundredtized = num * 100;
|
||||
const isWhole = hundredtized % 100 === 0;
|
||||
|
||||
return isWhole ? hundredtized / 100 : num;
|
||||
}
|
||||
|
||||
function padLeadingZeros(num, numOfLeadingZeros) {
|
||||
return num.toString().padStart(numOfLeadingZeros, '0');
|
||||
}
|
||||
|
||||
function toDoubleDigits(num) {
|
||||
return padLeadingZeros(num, 2);
|
||||
}
|
||||
|
||||
function toTripleDigits(num) {
|
||||
return padLeadingZeros(num, 3);
|
||||
}
|
||||
|
||||
function getPreciseDuration(value, { excludeMilliSeconds, useDayFormat } = {}) {
|
||||
let preciseDuration;
|
||||
const ms = value || 0;
|
||||
|
||||
const duration = [
|
||||
Math.floor(normalizeAge(ms / ONE_DAY)),
|
||||
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_DAY) / ONE_HOUR))),
|
||||
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_HOUR) / ONE_MINUTE))),
|
||||
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)))
|
||||
];
|
||||
if (!excludeMilliSeconds) {
|
||||
duration.push(toTripleDigits(Math.floor(normalizeAge(ms % ONE_SECOND))));
|
||||
}
|
||||
|
||||
if (useDayFormat) {
|
||||
// Format days as XD
|
||||
const days = duration.shift();
|
||||
if (days > 0) {
|
||||
preciseDuration = `${days}D ${duration.join(':')}`;
|
||||
} else {
|
||||
preciseDuration = duration.join(':');
|
||||
}
|
||||
} else {
|
||||
const days = toDoubleDigits(duration.shift());
|
||||
duration.unshift(days);
|
||||
preciseDuration = duration.join(':');
|
||||
}
|
||||
|
||||
return preciseDuration;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ This test suite is dedicated to tests which verify the basic operations surround
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
|
||||
import { copy, paste, selectAll } from '../../../../helper/hotkeys/hotkeys.js';
|
||||
import * as nbUtils from '../../../../helper/notebookUtils.js';
|
||||
import { expect, streamToString, test } from '../../../../pluginFixtures.js';
|
||||
|
||||
@@ -546,4 +547,53 @@ test.describe('Notebook entry tests', () => {
|
||||
);
|
||||
await expect(secondLineOfBlockquoteText).toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* Paste into notebook entry tests
|
||||
*/
|
||||
test('Can paste text into a notebook entry', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7686'
|
||||
});
|
||||
const TEST_TEXT = 'This is a test';
|
||||
const iterations = 20;
|
||||
const EXPECTED_TEXT = TEST_TEXT.repeat(iterations);
|
||||
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
await nbUtils.addNotebookEntry(page);
|
||||
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
|
||||
await selectAll(page);
|
||||
await copy(page);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
await paste(page);
|
||||
}
|
||||
await nbUtils.commitEntry(page);
|
||||
|
||||
await expect(page.locator(`text="${EXPECTED_TEXT}"`)).toBeVisible();
|
||||
});
|
||||
|
||||
test('Prevents pasting text into selected notebook entry if not editing', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/7686'
|
||||
});
|
||||
const TEST_TEXT = 'This is a test';
|
||||
|
||||
await page.goto(notebookObject.url);
|
||||
|
||||
await nbUtils.addNotebookEntry(page);
|
||||
await nbUtils.enterTextInLastEntry(page, TEST_TEXT);
|
||||
await selectAll(page);
|
||||
await copy(page);
|
||||
await paste(page);
|
||||
await nbUtils.commitEntry(page);
|
||||
|
||||
// This should not paste text into the entry
|
||||
await paste(page);
|
||||
|
||||
await expect(await page.locator(`text="${TEST_TEXT.repeat(1)}"`).count()).toEqual(1);
|
||||
await expect(await page.locator(`text="${TEST_TEXT.repeat(2)}"`).count()).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ TODO:
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const filePath = 'e2e/test-data/PerformanceDisplayLayout.json';
|
||||
const filePath = 'test-data/PerformanceDisplayLayout.json';
|
||||
|
||||
test.describe('Performance tests', () => {
|
||||
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||
|
||||
@@ -33,7 +33,7 @@ TODO:
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const notebookFilePath = 'e2e/test-data/PerformanceNotebook.json';
|
||||
const notebookFilePath = 'test-data/PerformanceNotebook.json';
|
||||
|
||||
test.describe('Performance tests', () => {
|
||||
test.beforeEach(async ({ page, browser }, testInfo) => {
|
||||
|
||||
@@ -33,7 +33,7 @@ test.describe('Visual - Inspector @ally @clock', () => {
|
||||
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
test.use({
|
||||
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
|
||||
storageState: 'test-data/overlay_plot_with_delay_storage.json',
|
||||
clockOptions: {
|
||||
now: MISSION_TIME,
|
||||
shouldAdvanceTime: true
|
||||
|
||||
@@ -35,7 +35,7 @@ test.describe('Visual - Controlled Clock @clock', () => {
|
||||
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
test.use({
|
||||
storageState: './e2e/test-data/overlay_plot_with_delay_storage.json',
|
||||
storageState: 'test-data/overlay_plot_with_delay_storage.json',
|
||||
clockOptions: {
|
||||
now: MISSION_TIME,
|
||||
shouldAdvanceTime: false //Don't advance the clock
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/*
|
||||
Collection of Visual Tests set to run in a default context with default Plugins. The tests within this suite
|
||||
are only meant to run against openmct's app.js started by `npm run start` within the
|
||||
`./e2e/playwright-visual.config.js` file.
|
||||
`playwright-visual.config.js` file.
|
||||
*/
|
||||
|
||||
import percySnapshot from '@percy/playwright';
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
const loadWebpackConfig = async () => {
|
||||
if (process.env.KARMA_DEBUG) {
|
||||
return {
|
||||
config: (await import('./.webpack/webpack.dev.js')).default,
|
||||
config: (await import('./.webpack/webpack.dev.mjs')).default,
|
||||
browsers: ['ChromeDebugging'],
|
||||
singleRun: false
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
config: (await import('./.webpack/webpack.coverage.js')).default,
|
||||
config: (await import('./.webpack/webpack.coverage.mjs')).default,
|
||||
browsers: ['ChromeHeadless'],
|
||||
singleRun: true
|
||||
};
|
||||
|
||||
42
package-lock.json
generated
42
package-lock.json
generated
@@ -8,13 +8,12 @@
|
||||
"name": "openmct",
|
||||
"version": "4.0.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"e2e"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.8.5",
|
||||
"@babel/eslint-parser": "7.23.3",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@types/d3-axis": "3.0.6",
|
||||
"@types/d3-scale": "4.0.8",
|
||||
"@types/d3-selection": "3.0.10",
|
||||
@@ -22,7 +21,6 @@
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "5.1.2",
|
||||
"@types/lodash": "4.17.0",
|
||||
"@types/sinonjs__fake-timers": "8.1.5",
|
||||
"@vue/compiler-sfc": "3.4.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
@@ -81,7 +79,6 @@
|
||||
"sanitize-html": "2.12.1",
|
||||
"sass": "1.71.1",
|
||||
"sass-loader": "14.1.1",
|
||||
"sinon": "17.0.0",
|
||||
"style-loader": "3.3.3",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"tiny-emitter": "2.1.0",
|
||||
@@ -99,6 +96,19 @@
|
||||
"node": ">=18.14.2 <22"
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"name": "openmct-e2e",
|
||||
"version": "4.0.0-next",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.8.5",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@types/sinonjs__fake-timers": "8.1.5",
|
||||
"sinon": "17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
|
||||
@@ -1496,9 +1506,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@percy/sdk-utils": {
|
||||
"version": "1.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.1.tgz",
|
||||
"integrity": "sha512-joS3i5wjFYXRSVL/NbUvip+bB7ErgwNjoDcID31l61y/QaSYUVCOxl/Fy4nvePJtHVyE1hpV0O7XO3tkoG908g==",
|
||||
"version": "1.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.2.tgz",
|
||||
"integrity": "sha512-cMFz8AjZ2KunN0dVwzA+Wosk4B+6G9dUkh2YPhYvqs0KLcCyYs3s91IzOQmtBOYwAUVja/W/u6XmBHw0jaxg0A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -1929,16 +1939,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
@@ -8549,6 +8549,10 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/openmct-e2e": {
|
||||
"resolved": "e2e",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||
|
||||
68
package.json
68
package.json
@@ -2,15 +2,21 @@
|
||||
"name": "openmct",
|
||||
"version": "4.0.0-next",
|
||||
"description": "The Open MCT core platform",
|
||||
"type": "module",
|
||||
"module": "dist/openmct.js",
|
||||
"main": "dist/openmct.js",
|
||||
"types": "dist/types/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/openmct.js",
|
||||
"require": "./dist/openmct.js"
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"e2e"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.8.5",
|
||||
"@babel/eslint-parser": "7.23.3",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@percy/cli": "1.27.4",
|
||||
"@percy/playwright": "1.0.4",
|
||||
"@playwright/test": "1.42.1",
|
||||
"@types/d3-axis": "3.0.6",
|
||||
"@types/d3-scale": "4.0.8",
|
||||
"@types/d3-selection": "3.0.10",
|
||||
@@ -18,7 +24,6 @@
|
||||
"@types/eventemitter3": "1.2.0",
|
||||
"@types/jasmine": "5.1.2",
|
||||
"@types/lodash": "4.17.0",
|
||||
"@types/sinonjs__fake-timers": "8.1.5",
|
||||
"@vue/compiler-sfc": "3.4.3",
|
||||
"babel-loader": "9.1.0",
|
||||
"babel-plugin-istanbul": "6.1.1",
|
||||
@@ -77,7 +82,6 @@
|
||||
"sanitize-html": "2.12.1",
|
||||
"sass": "1.71.1",
|
||||
"sass-loader": "14.1.1",
|
||||
"sinon": "17.0.0",
|
||||
"style-loader": "3.3.3",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"tiny-emitter": "2.1.0",
|
||||
@@ -92,39 +96,39 @@
|
||||
"webpack-merge": "5.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf ./dist ./node_modules ./coverage ./html-test-results ./test-results ./.nyc_output ",
|
||||
"start": "npx webpack serve --config ./.webpack/webpack.dev.js",
|
||||
"start:prod": "npx webpack serve --config ./.webpack/webpack.prod.js",
|
||||
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.js",
|
||||
"clean": "rm -rf ./dist ./node_modules ./coverage ./html-test-results ./test-results ./.nyc_output",
|
||||
"start": "npx webpack serve --config ./.webpack/webpack.dev.mjs",
|
||||
"start:prod": "npx webpack serve --config ./.webpack/webpack.prod.mjs",
|
||||
"start:coverage": "npx webpack serve --config ./.webpack/webpack.coverage.mjs",
|
||||
"lint:js": "eslint \"example/**/*.js\" \"src/**/*.js\" \"e2e/**/*.js\" \"openmct.js\" --max-warnings=0",
|
||||
"lint:vue": "eslint \"src/**/*.vue\"",
|
||||
"lint:spelling": "cspell \"**/*.{js,md,vue}\" --show-context --gitignore --quiet",
|
||||
"lint": "run-p \"lint:js -- {1}\" \"lint:vue -- {1}\" \"lint:spelling -- {1}\" --",
|
||||
"lint:fix": "eslint example src e2e --ext .js,.vue openmct.js --fix",
|
||||
"build:prod": "webpack --config ./.webpack/webpack.prod.js",
|
||||
"build:dev": "webpack --config ./.webpack/webpack.dev.js",
|
||||
"build:coverage": "webpack --config ./.webpack/webpack.coverage.js",
|
||||
"build:watch": "webpack --config ./.webpack/webpack.dev.js --watch",
|
||||
"build:prod": "webpack --config ./.webpack/webpack.prod.mjs",
|
||||
"build:dev": "webpack --config ./.webpack/webpack.dev.mjs",
|
||||
"build:coverage": "webpack --config ./.webpack/webpack.coverage.mjs",
|
||||
"build:watch": "webpack --config ./.webpack/webpack.dev.mjs --watch",
|
||||
"info": "npx envinfo --system --browsers --npmPackages --binaries --languages --markdown",
|
||||
"test": "karma start karma.conf.cjs",
|
||||
"test:debug": "KARMA_DEBUG=true karma start karma.conf.cjs",
|
||||
"test:e2e": "npx playwright test",
|
||||
"test:e2e:a11y": "npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep @a11y",
|
||||
"test:e2e:mobile": "npx playwright test --config=e2e/playwright-mobile.config.js",
|
||||
"test:e2e:couchdb": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @couchdb --workers=1",
|
||||
"test:e2e:stable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep-invert \"@unstable|@couchdb|@generatedata\"",
|
||||
"test:e2e:unstable": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @unstable",
|
||||
"test:e2e:local": "npx playwright test --config=e2e/playwright-local.config.js --project=chrome",
|
||||
"test:e2e:generatedata": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @generatedata",
|
||||
"test:e2e:checksnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --retries=0",
|
||||
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
|
||||
"test:e2e:visual:ci": "percy exec --config ./e2e/.percy.ci.yml --partial -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable",
|
||||
"test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual-a11y.config.js --grep-invert @unstable",
|
||||
"test:e2e:full": "npx playwright test --config=e2e/playwright-ci.config.js --grep-invert @couchdb",
|
||||
"test:e2e:watch": "npx playwright test --ui --config=e2e/playwright-watch.config.js",
|
||||
"test:perf:contract": "npx playwright test --config=e2e/playwright-performance-dev.config.js",
|
||||
"test:perf:localhost": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome",
|
||||
"test:perf:memory": "npx playwright test --config=e2e/playwright-performance-prod.config.js --project=chrome-memory",
|
||||
"test:e2e": "npm test --workspace e2e",
|
||||
"test:e2e:a11y": "npm test --workspace e2e -- --config=playwright-visual-a11y.config.js --project=chrome --grep @a11y",
|
||||
"test:e2e:mobile": "npm test --workspace e2e -- --config=playwright-mobile.config.js",
|
||||
"test:e2e:couchdb": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @couchdb --workers=1",
|
||||
"test:e2e:stable": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep-invert \"@unstable|@couchdb|@generatedata\"",
|
||||
"test:e2e:unstable": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @unstable",
|
||||
"test:e2e:local": "npm test --workspace e2e -- --config=playwright-local.config.js --project=chrome",
|
||||
"test:e2e:generatedata": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @generatedata",
|
||||
"test:e2e:checksnapshots": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @snapshot --retries=0",
|
||||
"test:e2e:updatesnapshots": "npm test --workspace e2e -- --config=playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
|
||||
"test:e2e:visual:ci": "npm run test:visual --workspace e2e -- --config .percy.ci.yml --partial -- npx playwright test --config=playwright-visual-a11y.config.js --project=chrome --grep-invert @unstable",
|
||||
"test:e2e:visual:full": "npm run test:visual --workspace e2e -- --config .percy.nightly.yml -- npx playwright test --config=playwright-visual-a11y.config.js --grep-invert @unstable",
|
||||
"test:e2e:full": "npm test --workspace e2e -- --config=playwright-ci.config.js --grep-invert @couchdb",
|
||||
"test:e2e:watch": "npm test --workspace e2e -- --ui --config=playwright-watch.config.js",
|
||||
"test:perf:contract": "npm test --workspace e2e -- --config=playwright-performance-dev.config.js",
|
||||
"test:perf:localhost": "npm test --workspace e2e -- --config=playwright-performance-prod.config.js --project=chrome",
|
||||
"test:perf:memory": "npm test --workspace e2e -- --config=playwright-performance-prod.config.js --project=chrome-memory",
|
||||
"update-about-dialog-copyright": "perl -pi -e 's/20\\d\\d\\-202\\d/2014\\-2023/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\\-2024/gm'",
|
||||
"cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e",
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
|
||||
import TimeContext from './TimeContext.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./TimeAPI').TimeConductorBounds} TimeConductorBounds
|
||||
*/
|
||||
|
||||
/**
|
||||
* The GlobalContext handles getting and setting time of the openmct application in general.
|
||||
* Views will use this context unless they specify an alternate/independent time context
|
||||
@@ -38,12 +42,10 @@ class GlobalTimeContext extends TimeContext {
|
||||
* Get or set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||
* @param {TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeAPI~bounds
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
* @returns {TimeConductorBounds}
|
||||
* @override
|
||||
*/
|
||||
bounds(newBounds) {
|
||||
if (arguments.length > 0) {
|
||||
@@ -61,9 +63,9 @@ class GlobalTimeContext extends TimeContext {
|
||||
|
||||
/**
|
||||
* Update bounds based on provided time and current offsets
|
||||
* @private
|
||||
* @param {number} timestamp A time from which bounds will be calculated
|
||||
* using current offsets.
|
||||
* @override
|
||||
*/
|
||||
tick(timestamp) {
|
||||
super.tick.call(this, ...arguments);
|
||||
@@ -81,11 +83,8 @@ class GlobalTimeContext extends TimeContext {
|
||||
* be manipulated by the user from the time conductor or from other views.
|
||||
* The time of interest can effectively be unset by assigning a value of
|
||||
* 'undefined'.
|
||||
* @fires module:openmct.TimeAPI~timeOfInterest
|
||||
* @param newTOI
|
||||
* @returns {number} the current time of interest
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method timeOfInterest
|
||||
*/
|
||||
timeOfInterest(newTOI) {
|
||||
if (arguments.length > 0) {
|
||||
@@ -93,8 +92,7 @@ class GlobalTimeContext extends TimeContext {
|
||||
/**
|
||||
* The Time of Interest has moved.
|
||||
* @event timeOfInterest
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {number} Current time of interest
|
||||
* @property {number} timeOfInterest time of interest
|
||||
*/
|
||||
this.emit('timeOfInterest', this.toi);
|
||||
}
|
||||
|
||||
@@ -23,19 +23,36 @@
|
||||
import { MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js';
|
||||
import TimeContext from './TimeContext.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./TimeAPI.js').default} TimeAPI
|
||||
* @typedef {import('./GlobalTimeContext.js').default} GlobalTimeContext
|
||||
* @typedef {import('./TimeAPI.js').TimeSystem} TimeSystem
|
||||
* @typedef {import('./TimeContext.js').Mode} Mode
|
||||
* @typedef {import('./TimeContext.js').TimeConductorBounds} TimeConductorBounds
|
||||
* @typedef {import('./TimeAPI.js').ClockOffsets} ClockOffsets
|
||||
*/
|
||||
|
||||
/**
|
||||
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
|
||||
* Views will use the GlobalTimeContext unless they specify an alternate/independent time context here.
|
||||
*/
|
||||
class IndependentTimeContext extends TimeContext {
|
||||
/**
|
||||
* @param {import('openmct').OpenMCT} openmct - The Open MCT application instance.
|
||||
* @param {TimeAPI & GlobalTimeContext} globalTimeContext - The global time context.
|
||||
* @param {import('openmct').ObjectPath} objectPath - The path of objects.
|
||||
*/
|
||||
constructor(openmct, globalTimeContext, objectPath) {
|
||||
super();
|
||||
/** @type {any} */
|
||||
this.openmct = openmct;
|
||||
/** @type {Function[]} */
|
||||
this.unlisteners = [];
|
||||
/** @type {TimeAPI & GlobalTimeContext | undefined} */
|
||||
this.globalTimeContext = globalTimeContext;
|
||||
// We always start with the global time context.
|
||||
// This upstream context will be undefined when an independent time context is added later.
|
||||
/** @type {TimeAPI & GlobalTimeContext | undefined} */
|
||||
this.upstreamTimeContext = this.globalTimeContext;
|
||||
/** @type {Array<any>} */
|
||||
this.objectPath = objectPath;
|
||||
this.refreshContext = this.refreshContext.bind(this);
|
||||
this.resetContext = this.resetContext.bind(this);
|
||||
@@ -47,6 +64,10 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @override
|
||||
*/
|
||||
bounds() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.bounds(...arguments);
|
||||
@@ -55,6 +76,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getBounds() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.getBounds();
|
||||
@@ -63,6 +87,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setBounds() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.setBounds(...arguments);
|
||||
@@ -71,6 +98,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
tick() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.tick(...arguments);
|
||||
@@ -79,6 +109,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
clockOffsets() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.clockOffsets(...arguments);
|
||||
@@ -87,6 +120,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getClockOffsets() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.getClockOffsets();
|
||||
@@ -95,6 +131,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setClockOffsets() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.setClockOffsets(...arguments);
|
||||
@@ -103,12 +142,24 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} newTOI
|
||||
* @returns {number}
|
||||
*/
|
||||
timeOfInterest(newTOI) {
|
||||
return this.globalTimeContext.timeOfInterest(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {TimeSystem | string} timeSystemOrKey
|
||||
* @param {TimeConductorBounds} bounds
|
||||
* @returns {TimeSystem}
|
||||
* @override
|
||||
*/
|
||||
timeSystem(timeSystemOrKey, bounds) {
|
||||
return this.globalTimeContext.timeSystem(...arguments);
|
||||
return this.globalTimeContext.setTimeSystem(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,6 +167,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getTimeSystem
|
||||
* @override
|
||||
*/
|
||||
getTimeSystem() {
|
||||
return this.globalTimeContext.getTimeSystem();
|
||||
@@ -246,6 +298,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
/**
|
||||
* Get the current mode.
|
||||
* @return {Mode} the current mode;
|
||||
* @override
|
||||
*/
|
||||
getMode() {
|
||||
if (this.upstreamTimeContext) {
|
||||
@@ -259,9 +312,8 @@ class IndependentTimeContext extends TimeContext {
|
||||
* Set the mode to either fixed or realtime.
|
||||
*
|
||||
* @param {Mode} mode The mode to activate
|
||||
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Mode} the currently active mode;
|
||||
* @param {TimeConductorBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
|
||||
* @return {Mode | undefined} the currently active mode;
|
||||
*/
|
||||
setMode(mode, offsetsOrBounds) {
|
||||
if (!mode) {
|
||||
@@ -299,6 +351,10 @@ class IndependentTimeContext extends TimeContext {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
* @override
|
||||
*/
|
||||
isRealTime() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.isRealTime(...arguments);
|
||||
@@ -307,6 +363,10 @@ class IndependentTimeContext extends TimeContext {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
* @override
|
||||
*/
|
||||
now() {
|
||||
if (this.upstreamTimeContext) {
|
||||
return this.upstreamTimeContext.now(...arguments);
|
||||
@@ -343,6 +403,9 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.unlisteners = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the time context to the global time context
|
||||
*/
|
||||
resetContext() {
|
||||
if (this.upstreamTimeContext) {
|
||||
this.stopFollowingTimeContext();
|
||||
@@ -352,6 +415,7 @@ class IndependentTimeContext extends TimeContext {
|
||||
|
||||
/**
|
||||
* Refresh the time context, following any upstream time contexts as necessary
|
||||
* @param {string} [viewKey] The key of the view to refresh
|
||||
*/
|
||||
refreshContext(viewKey) {
|
||||
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
|
||||
@@ -366,14 +430,21 @@ class IndependentTimeContext extends TimeContext {
|
||||
this.followTimeContext();
|
||||
|
||||
// Emit bounds so that views that are changing context get the upstream bounds
|
||||
this.emit('bounds', this.bounds());
|
||||
this.emit('bounds', this.getBounds());
|
||||
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if this time context has an independent context, false otherwise
|
||||
*/
|
||||
hasOwnContext() {
|
||||
return this.upstreamTimeContext === undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upstream time context of this time context
|
||||
* @returns {TimeAPI & GlobalTimeContext | undefined} The upstream time context
|
||||
*/
|
||||
getUpstreamContext() {
|
||||
// If a view has an independent context, don't return an upstream context
|
||||
// Be aware that when a new independent time context is created, we assign the global context as default
|
||||
|
||||
@@ -25,6 +25,41 @@ import IndependentTimeContext from '@/api/time/IndependentTimeContext';
|
||||
|
||||
import GlobalTimeContext from './GlobalTimeContext.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./TimeContext.js').default} TimeContext
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./TimeContext.js').TimeConductorBounds} TimeConductorBounds
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./TimeContext.js').ClockOffsets} ClockOffsets
|
||||
*/
|
||||
|
||||
/**
|
||||
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
||||
* MCT supports multiple different types of time values, although all are
|
||||
* intrinsically represented by numbers, the meaning of those numbers can
|
||||
* differ depending on context.
|
||||
*
|
||||
* A default time system is provided by Open MCT in the form of the {@link UTCTimeSystem},
|
||||
* which represents integer values as ms in the Unix epoch. An example of
|
||||
* another time system might be "sols" for a Martian mission. TimeSystems do
|
||||
* not address the issue of converting between time systems.
|
||||
*
|
||||
* @typedef {Object} TimeSystem
|
||||
* @property {string} key A unique identifier
|
||||
* @property {string} name A human-readable descriptor
|
||||
* @property {string} [cssClass] Specify a css class defining an icon for
|
||||
* this time system. This will be visible next to the time system in the
|
||||
* menu in the Time Conductor
|
||||
* @property {string} timeFormat The key of a format to use when displaying
|
||||
* discrete timestamps from this time system
|
||||
* @property {string} [durationFormat] The key of a format to use when
|
||||
* displaying a duration or relative span of time in this time system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The public API for setting and querying the temporal state of the
|
||||
* application. The concept of time is integral to Open MCT, and at least
|
||||
@@ -41,8 +76,8 @@ import GlobalTimeContext from './GlobalTimeContext.js';
|
||||
* fired when properties of the time conductor change, which are documented
|
||||
* below.
|
||||
*
|
||||
* @interface
|
||||
* @memberof module:openmct
|
||||
* @class
|
||||
* @extends {GlobalTimeContext}
|
||||
*/
|
||||
class TimeAPI extends GlobalTimeContext {
|
||||
constructor(openmct) {
|
||||
@@ -51,33 +86,9 @@ class TimeAPI extends GlobalTimeContext {
|
||||
this.independentContexts = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* A TimeSystem provides meaning to the values returned by the TimeAPI. Open
|
||||
* MCT supports multiple different types of time values, although all are
|
||||
* intrinsically represented by numbers, the meaning of those numbers can
|
||||
* differ depending on context.
|
||||
*
|
||||
* A default time system is provided by Open MCT in the form of the {@link UTCTimeSystem},
|
||||
* which represents integer values as ms in the Unix epoch. An example of
|
||||
* another time system might be "sols" for a Martian mission. TimeSystems do
|
||||
* not address the issue of converting between time systems.
|
||||
*
|
||||
* @typedef {Object} TimeSystem
|
||||
* @property {string} key A unique identifier
|
||||
* @property {string} name A human-readable descriptor
|
||||
* @property {string} [cssClass] Specify a css class defining an icon for
|
||||
* this time system. This will be visible next to the time system in the
|
||||
* menu in the Time Conductor
|
||||
* @property {string} timeFormat The key of a format to use when displaying
|
||||
* discrete timestamps from this time system
|
||||
* @property {string} [durationFormat] The key of a format to use when
|
||||
* displaying a duration or relative span of time in this time system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register a new time system. Once registered it can activated using
|
||||
* {@link TimeAPI.timeSystem}, and can be referenced via its key in [Time Conductor configuration](@link https://github.com/nasa/openmct/blob/master/API.md#time-conductor).
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @param {TimeSystem} timeSystem A time system object.
|
||||
*/
|
||||
addTimeSystem(timeSystem) {
|
||||
@@ -109,7 +120,6 @@ class TimeAPI extends GlobalTimeContext {
|
||||
|
||||
/**
|
||||
* Register a new Clock.
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @param {Clock} clock
|
||||
*/
|
||||
addClock(clock) {
|
||||
@@ -117,9 +127,7 @@ class TimeAPI extends GlobalTimeContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @returns {Clock[]}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
*/
|
||||
getAllClocks() {
|
||||
return Array.from(this.clocks.values());
|
||||
@@ -128,11 +136,9 @@ class TimeAPI extends GlobalTimeContext {
|
||||
/**
|
||||
* Get or set an independent time context which follows the TimeAPI timeSystem,
|
||||
* but with different offsets for a given domain object
|
||||
* @param {key | string} key The identifier key of the domain object these offsets are set for
|
||||
* @param {ClockOffsets | TimeBounds} value This maintains a sliding time window of a fixed width that automatically updates
|
||||
* @param {string} key The identifier key of the domain object these offsets are set for
|
||||
* @param {ClockOffsets | TimeConductorBounds} value This maintains a sliding time window of a fixed width that automatically updates
|
||||
* @param {key | string} clockKey the real time clock key currently in use
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method addIndependentTimeContext
|
||||
*/
|
||||
addIndependentContext(key, value, clockKey) {
|
||||
let timeContext = this.getIndependentContext(key);
|
||||
@@ -159,9 +165,8 @@ class TimeAPI extends GlobalTimeContext {
|
||||
/**
|
||||
* Get the independent time context which follows the TimeAPI timeSystem,
|
||||
* but with different offsets.
|
||||
* @param {key | string} key The identifier key of the domain object these offsets
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getIndependentTimeContext
|
||||
* @param {string} key The identifier key of the domain object these offsets
|
||||
* @returns {IndependentTimeContext} The independent time context
|
||||
*/
|
||||
getIndependentContext(key) {
|
||||
return this.independentContexts.get(key);
|
||||
@@ -170,9 +175,8 @@ class TimeAPI extends GlobalTimeContext {
|
||||
/**
|
||||
* Get the a timeContext for a view based on it's objectPath. If there is any object in the objectPath with an independent time context, it will be returned.
|
||||
* Otherwise, the global time context will be returned.
|
||||
* @param { Array } objectPath The view's objectPath
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getContextForView
|
||||
* @param {Array} objectPath The view's objectPath
|
||||
* @returns {TimeContext | GlobalTimeContext} The time context
|
||||
*/
|
||||
getContextForView(objectPath) {
|
||||
if (!objectPath || !Array.isArray(objectPath)) {
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('The Time API', function () {
|
||||
expect(api.timeOfInterest()).toBe(toi);
|
||||
});
|
||||
|
||||
it('Allows setting of valid bounds', function () {
|
||||
it('[Legacy TimeAPI]: Allows setting of valid bounds', function () {
|
||||
bounds = {
|
||||
start: 0,
|
||||
end: 1
|
||||
@@ -67,7 +67,17 @@ describe('The Time API', function () {
|
||||
expect(api.bounds()).toEqual(bounds);
|
||||
});
|
||||
|
||||
it('Disallows setting of invalid bounds', function () {
|
||||
it('Allows setting of valid bounds', function () {
|
||||
bounds = {
|
||||
start: 0,
|
||||
end: 1
|
||||
};
|
||||
expect(api.getBounds()).not.toBe(bounds);
|
||||
expect(api.setBounds.bind(api, bounds)).not.toThrow();
|
||||
expect(api.getBounds()).toEqual(bounds);
|
||||
});
|
||||
|
||||
it('[Legacy TimeAPI]: Disallows setting of invalid bounds', function () {
|
||||
bounds = {
|
||||
start: 1,
|
||||
end: 0
|
||||
@@ -82,7 +92,22 @@ describe('The Time API', function () {
|
||||
expect(api.bounds()).not.toEqual(bounds);
|
||||
});
|
||||
|
||||
it('Allows setting of previously registered time system with bounds', function () {
|
||||
it('Disallows setting of invalid bounds', function () {
|
||||
bounds = {
|
||||
start: 1,
|
||||
end: 0
|
||||
};
|
||||
expect(api.getBounds()).not.toEqual(bounds);
|
||||
expect(api.setBounds.bind(api, bounds)).toThrow();
|
||||
expect(api.getBounds()).not.toEqual(bounds);
|
||||
|
||||
bounds = { start: 1 };
|
||||
expect(api.getBounds()).not.toEqual(bounds);
|
||||
expect(api.setBounds.bind(api, bounds)).toThrow();
|
||||
expect(api.getBounds()).not.toEqual(bounds);
|
||||
});
|
||||
|
||||
it('[Legacy TimeAPI]: Allows setting of previously registered time system with bounds', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
expect(api.timeSystem()).not.toBe(timeSystem);
|
||||
expect(function () {
|
||||
@@ -91,7 +116,16 @@ describe('The Time API', function () {
|
||||
expect(api.timeSystem()).toEqual(timeSystem);
|
||||
});
|
||||
|
||||
it('Disallows setting of time system without bounds', function () {
|
||||
it('Allows setting of previously registered time system with bounds', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
expect(api.getTimeSystem()).not.toBe(timeSystem);
|
||||
expect(function () {
|
||||
api.setTimeSystem(timeSystem, bounds);
|
||||
}).not.toThrow();
|
||||
expect(api.getTimeSystem()).toEqual(timeSystem);
|
||||
});
|
||||
|
||||
it('[Legacy TimeAPI]: Disallows setting of time system without bounds', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
expect(api.timeSystem()).not.toBe(timeSystem);
|
||||
expect(function () {
|
||||
@@ -100,6 +134,32 @@ describe('The Time API', function () {
|
||||
expect(api.timeSystem()).not.toBe(timeSystem);
|
||||
});
|
||||
|
||||
it('Allows setting of time system without bounds', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
expect(api.getTimeSystem()).not.toBe(timeSystem);
|
||||
expect(function () {
|
||||
api.setTimeSystem(timeSystemKey);
|
||||
}).not.toThrow();
|
||||
expect(api.getTimeSystem()).not.toBe(timeSystem);
|
||||
});
|
||||
|
||||
it('Disallows setting of invalid time system', function () {
|
||||
expect(function () {
|
||||
api.setTimeSystem();
|
||||
}).toThrow();
|
||||
expect(function () {
|
||||
api.setTimeSystem('invalidTimeSystemKey');
|
||||
}).toThrow();
|
||||
expect(function () {
|
||||
api.setTimeSystem({
|
||||
key: 'invalidTimeSystemKey'
|
||||
});
|
||||
}).toThrow();
|
||||
expect(function () {
|
||||
api.setTimeSystem(42);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('allows setting of timesystem without bounds with clock', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
api.addClock(clock);
|
||||
@@ -114,7 +174,7 @@ describe('The Time API', function () {
|
||||
expect(api.timeSystem()).toEqual(timeSystem);
|
||||
});
|
||||
|
||||
it('Emits an event when time system changes', function () {
|
||||
it('Emits a legacy event when time system changes', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
api.on('timeSystem', eventListener);
|
||||
@@ -122,6 +182,14 @@ describe('The Time API', function () {
|
||||
expect(eventListener).toHaveBeenCalledWith(timeSystem);
|
||||
});
|
||||
|
||||
it('Emits an event when time system changes', function () {
|
||||
api.addTimeSystem(timeSystem);
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
api.on('timeSystemChanged', eventListener);
|
||||
api.timeSystem(timeSystemKey, bounds);
|
||||
expect(eventListener).toHaveBeenCalledWith(timeSystem);
|
||||
});
|
||||
|
||||
it('Emits an event when time of interest changes', function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
api.on('timeOfInterest', eventListener);
|
||||
@@ -129,13 +197,20 @@ describe('The Time API', function () {
|
||||
expect(eventListener).toHaveBeenCalledWith(toi);
|
||||
});
|
||||
|
||||
it('Emits an event when bounds change', function () {
|
||||
it('Emits a legacy event when bounds change', function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
api.on('bounds', eventListener);
|
||||
api.bounds(bounds);
|
||||
expect(eventListener).toHaveBeenCalledWith(bounds, false);
|
||||
});
|
||||
|
||||
it('Emits an event when bounds change', function () {
|
||||
expect(eventListener).not.toHaveBeenCalled();
|
||||
api.on('boundsChanged', eventListener);
|
||||
api.bounds(bounds);
|
||||
expect(eventListener).toHaveBeenCalledWith(bounds, false);
|
||||
});
|
||||
|
||||
it('If bounds are set and TOI lies inside them, do not change TOI', function () {
|
||||
api.timeOfInterest(6);
|
||||
api.bounds({
|
||||
@@ -154,13 +229,39 @@ describe('The Time API', function () {
|
||||
expect(api.timeOfInterest()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Maintains delta during tick', function () {});
|
||||
it('Maintains delta during tick', function () {
|
||||
const initialBounds = { start: 100, end: 200 };
|
||||
api.bounds(initialBounds);
|
||||
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
|
||||
mockTickSource.key = 'mct';
|
||||
mockTickSource.currentValue.and.returnValue(150);
|
||||
api.addClock(mockTickSource);
|
||||
api.clock('mct', { start: 0, end: 100 });
|
||||
|
||||
it('Allows registered time system to be activated', function () {});
|
||||
// Simulate a tick event
|
||||
const tickCallback = mockTickSource.on.calls.mostRecent().args[1];
|
||||
tickCallback(150);
|
||||
|
||||
const newBounds = api.bounds();
|
||||
expect(newBounds.end - newBounds.start).toEqual(initialBounds.end - initialBounds.start);
|
||||
});
|
||||
|
||||
it('Allows registered time system to be activated', function () {
|
||||
api.addClock(clock);
|
||||
api.clock(clockKey, { start: 0, end: 100 });
|
||||
api.addTimeSystem(timeSystem);
|
||||
api.timeSystem(timeSystemKey);
|
||||
expect(api.timeSystem().key).toEqual(timeSystemKey);
|
||||
});
|
||||
|
||||
it('Allows a registered tick source to be activated', function () {
|
||||
const mockTickSource = jasmine.createSpyObj('mockTickSource', ['on', 'off', 'currentValue']);
|
||||
mockTickSource.key = 'mockTickSource';
|
||||
mockTickSource.currentValue.and.returnValue(50);
|
||||
api.addClock(mockTickSource);
|
||||
api.clock(mockTickSource.key, { start: 0, end: 100 });
|
||||
|
||||
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
|
||||
});
|
||||
|
||||
describe(' when enabling a tick source', function () {
|
||||
@@ -184,7 +285,7 @@ describe('The Time API', function () {
|
||||
api.addClock(anotherMockTickSource);
|
||||
});
|
||||
|
||||
it('sets bounds based on current value', function () {
|
||||
it('[Legacy TimeAPI]: sets bounds based on current value', function () {
|
||||
api.clock('mts', mockOffsets);
|
||||
expect(api.bounds()).toEqual({
|
||||
start: 10,
|
||||
@@ -192,23 +293,46 @@ describe('The Time API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('a new tick listener is registered', function () {
|
||||
it('does not set bounds based on current value', function () {
|
||||
api.setClock('mts');
|
||||
expect(api.getBounds()).toEqual({});
|
||||
});
|
||||
|
||||
it('does not set invalid clock', function () {
|
||||
expect(function () {
|
||||
api.setClock();
|
||||
}).toThrow();
|
||||
expect(function () {
|
||||
api.setClock({});
|
||||
}).toThrow();
|
||||
expect(function () {
|
||||
api.setClock('invalidClockKey');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('[Legacy TimeAPI]: a new tick listener is registered', function () {
|
||||
api.clock('mts', mockOffsets);
|
||||
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('a new tick listener is registered', function () {
|
||||
api.setClock('mts', mockOffsets);
|
||||
expect(mockTickSource.on).toHaveBeenCalledWith('tick', jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('listener of existing tick source is reregistered', function () {
|
||||
api.clock('mts', mockOffsets);
|
||||
api.clock('amts', mockOffsets);
|
||||
expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function));
|
||||
});
|
||||
|
||||
xit('Allows the active clock to be set and unset', function () {
|
||||
it('[Legacy TimeAPI]: Allows the active clock to be set and unset', function () {
|
||||
expect(api.clock()).toBeUndefined();
|
||||
api.clock('mts', mockOffsets);
|
||||
expect(api.clock()).toBeDefined();
|
||||
// api.stopClock();
|
||||
// expect(api.clock()).toBeUndefined();
|
||||
// Unset the clock
|
||||
api.stopClock();
|
||||
expect(api.clock()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Provides a default time context', () => {
|
||||
|
||||
@@ -20,26 +20,89 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
import { FIXED_MODE_KEY, MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../../utils/clock/DefaultClock.js').default} Clock
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('./TimeAPI.js').TimeSystem} TimeSystem
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} TimeConductorBounds
|
||||
* @property {number} start The start time displayed by the time conductor
|
||||
* in ms since epoch. Epoch determined by currently active time system
|
||||
* @property {number} end The end time displayed by the time conductor in ms
|
||||
* since epoch.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clock offsets are used to calculate temporal bounds when the system is
|
||||
* ticking on a clock source.
|
||||
*
|
||||
* @typedef {Object} ClockOffsets
|
||||
* @property {number} start A time span relative to the current value of the
|
||||
* ticking clock, from which start bounds will be calculated. This value must
|
||||
* be < 0. When a clock is active, bounds will be calculated automatically
|
||||
* based on the value provided by the clock, and the defined clock offsets.
|
||||
* @property {number} end A time span relative to the current value of the
|
||||
* ticking clock, from which end bounds will be calculated. This value must
|
||||
* be >= 0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ValidationResult
|
||||
* @property {boolean} valid Result of the validation - true or false.
|
||||
* @property {string} message An error message if valid is false.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {'fixed' | 'realtime'} Mode The time conductor mode.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class TimeContext
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class TimeContext extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
//The Time System
|
||||
/**
|
||||
* The time systems available to the TimeAPI.
|
||||
* @type {Map<string, TimeSystem>}
|
||||
*/
|
||||
this.timeSystems = new Map();
|
||||
|
||||
/**
|
||||
* The currently applied time system.
|
||||
* @type {TimeSystem | undefined}
|
||||
*/
|
||||
this.system = undefined;
|
||||
|
||||
/**
|
||||
* The clocks available to the TimeAPI.
|
||||
* @type {Map<string, import('../../utils/clock/DefaultClock.js').default>}
|
||||
*/
|
||||
this.clocks = new Map();
|
||||
|
||||
/**
|
||||
* The current bounds of the time conductor.
|
||||
* @type {TimeConductorBounds}
|
||||
*/
|
||||
this.boundsVal = {
|
||||
start: undefined,
|
||||
end: undefined
|
||||
};
|
||||
|
||||
/**
|
||||
* The currently active clock.
|
||||
* @type {Clock | undefined}
|
||||
*/
|
||||
this.activeClock = undefined;
|
||||
this.offsets = undefined;
|
||||
this.mode = undefined;
|
||||
@@ -51,11 +114,9 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* Get or set the time system of the TimeAPI.
|
||||
* @param {TimeSystem | string} timeSystemOrKey
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeAPI~timeSystem
|
||||
* @param {TimeConductorBounds} bounds
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method timeSystem
|
||||
* @deprecated This method is deprecated. Use "getTimeSystem" and "setTimeSystem" instead.
|
||||
*/
|
||||
timeSystem(timeSystemOrKey, bounds) {
|
||||
this.#warnMethodDeprecated('"timeSystem"', '"getTimeSystem" and "setTimeSystem"');
|
||||
@@ -101,11 +162,8 @@ class TimeContext extends EventEmitter {
|
||||
* The time system used by the time
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds.
|
||||
*
|
||||
* @event module:openmct.TimeAPI~timeSystem
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
* @type {TimeSystem}
|
||||
*/
|
||||
const system = this.#copy(this.system);
|
||||
this.emit('timeSystem', system);
|
||||
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, system);
|
||||
@@ -118,21 +176,11 @@ class TimeContext extends EventEmitter {
|
||||
return this.system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clock offsets are used to calculate temporal bounds when the system is
|
||||
* ticking on a clock source.
|
||||
*
|
||||
* @typedef {Object} ValidationResult
|
||||
* @property {boolean} valid Result of the validation - true or false.
|
||||
* @property {string} message An error message if valid is false.
|
||||
*/
|
||||
/**
|
||||
* Validate the given bounds. This can be used for pre-validation of bounds,
|
||||
* for example by views validating user inputs.
|
||||
* @param {TimeBounds} bounds The start and end time of the conductor.
|
||||
* @param {TimeConductorBounds} bounds The start and end time of the conductor.
|
||||
* @returns {ValidationResult} A validation error, or true if valid
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method validateBounds
|
||||
*/
|
||||
validateBounds(bounds) {
|
||||
if (
|
||||
@@ -162,12 +210,10 @@ class TimeContext extends EventEmitter {
|
||||
* Get or set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||
* @param {TimeConductorBounds} [newBounds] The new bounds to set. If not provided, current bounds will be returned.
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeAPI~bounds
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
* @returns {TimeConductorBounds} The current bounds of the time conductor.
|
||||
* @deprecated This method is deprecated. Use "getBounds" and "setBounds" instead.
|
||||
*/
|
||||
bounds(newBounds) {
|
||||
this.#warnMethodDeprecated('"bounds"', '"getBounds" and "setBounds"');
|
||||
@@ -183,7 +229,6 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||
* a "tick" event (ie. was an automatic update), false otherwise.
|
||||
@@ -200,9 +245,7 @@ class TimeContext extends EventEmitter {
|
||||
* Validate the given offsets. This can be used for pre-validation of
|
||||
* offsets, for example by views validating user inputs.
|
||||
* @param {ClockOffsets} offsets The start and end offsets from a 'now' value.
|
||||
* @returns { ValidationResult } A validation error, and true/false if valid or not
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method validateOffsets
|
||||
* @returns {ValidationResult} A validation error, and true/false if valid or not
|
||||
*/
|
||||
validateOffsets(offsets) {
|
||||
if (
|
||||
@@ -228,34 +271,13 @@ class TimeContext extends EventEmitter {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} TimeBounds
|
||||
* @property {number} start The start time displayed by the time conductor
|
||||
* in ms since epoch. Epoch determined by currently active time system
|
||||
* @property {number} end The end time displayed by the time conductor in ms
|
||||
* since epoch.
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
*/
|
||||
|
||||
/**
|
||||
* Clock offsets are used to calculate temporal bounds when the system is
|
||||
* ticking on a clock source.
|
||||
*
|
||||
* @typedef {Object} ClockOffsets
|
||||
* @property {number} start A time span relative to the current value of the
|
||||
* ticking clock, from which start bounds will be calculated. This value must
|
||||
* be < 0. When a clock is active, bounds will be calculated automatically
|
||||
* based on the value provided by the clock, and the defined clock offsets.
|
||||
* @property {number} end A time span relative to the current value of the
|
||||
* ticking clock, from which end bounds will be calculated. This value must
|
||||
* be >= 0.
|
||||
*/
|
||||
/**
|
||||
* Get or set the currently applied clock offsets. If no parameter is provided,
|
||||
* the current value will be returned. If provided, the new value will be
|
||||
* used as the new clock offsets.
|
||||
* @param {ClockOffsets} offsets
|
||||
* @returns {ClockOffsets}
|
||||
* @param {ClockOffsets} [offsets] The new clock offsets to set. If not provided, current offsets will be returned.
|
||||
* @returns {ClockOffsets} The current clock offsets.
|
||||
* @deprecated This method is deprecated. Use "getClockOffsets" and "setClockOffsets" instead.
|
||||
*/
|
||||
clockOffsets(offsets) {
|
||||
this.#warnMethodDeprecated('"clockOffsets"', '"getClockOffsets" and "setClockOffsets"');
|
||||
@@ -293,6 +315,7 @@ class TimeContext extends EventEmitter {
|
||||
* Stop following the currently active clock. This will
|
||||
* revert all views to showing a static time frame defined by the current
|
||||
* bounds.
|
||||
* @deprecated This method is deprecated.
|
||||
*/
|
||||
stopClock() {
|
||||
this.#warnMethodDeprecated('"stopClock"');
|
||||
@@ -304,12 +327,14 @@ class TimeContext extends EventEmitter {
|
||||
* Set the active clock. Tick source will be immediately subscribed to
|
||||
* and ticking will begin. Offsets from 'now' must also be provided.
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @param {string|Clock} keyOrClock The clock to activate, or its key
|
||||
* @param {ClockOffsets} offsets on each tick these will be used to calculate
|
||||
* the start and end bounds. This maintains a sliding time window of a fixed
|
||||
* width that automatically updates.
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Clock} the currently active clock;
|
||||
* (Legacy) Emits a "clock" event with the new clock.
|
||||
* Emits a "clockChanged" event with the new clock.
|
||||
* @return {Clock|undefined} the currently active clock; undefined if in fixed mode
|
||||
* @deprecated This method is deprecated. Use "getClock" and "setClock" instead.
|
||||
*/
|
||||
clock(keyOrClock, offsets) {
|
||||
this.#warnMethodDeprecated('"clock"', '"getClock" and "setClock"');
|
||||
@@ -339,7 +364,6 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
@@ -361,7 +385,7 @@ class TimeContext extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bounds based on provided time and current offsets
|
||||
* Update bounds based on provided time and current offsets.
|
||||
* @param {number} timestamp A time from which bounds will be calculated
|
||||
* using current offsets.
|
||||
*/
|
||||
@@ -385,8 +409,6 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* Get the timestamp of the current clock
|
||||
* @returns {number} current timestamp of current clock regardless of mode
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method now
|
||||
*/
|
||||
|
||||
now() {
|
||||
@@ -396,8 +418,6 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* Get the time system of the TimeAPI.
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getTimeSystem
|
||||
*/
|
||||
getTimeSystem() {
|
||||
return this.system;
|
||||
@@ -405,12 +425,9 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Set the time system of the TimeAPI.
|
||||
* @param {TimeSystem | string} timeSystemOrKey
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
|
||||
* @fires module:openmct.TimeAPI~timeSystem
|
||||
* @returns {TimeSystem} The currently applied time system
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method setTimeSystem
|
||||
* Emits a "timeSystem" event with the new time system.
|
||||
* @param {TimeSystem | string} timeSystemOrKey The time system to set, or its key
|
||||
* @param {TimeConductorBounds} [bounds] Optional bounds to set
|
||||
*/
|
||||
setTimeSystem(timeSystemOrKey, bounds) {
|
||||
if (timeSystemOrKey === undefined) {
|
||||
@@ -441,7 +458,6 @@ class TimeContext extends EventEmitter {
|
||||
* conductor has changed. A change in Time System will always be
|
||||
* followed by a bounds event specifying new query bounds.
|
||||
*
|
||||
* @event module:openmct.TimeAPI~timeSystem
|
||||
* @property {TimeSystem} The value of the currently applied
|
||||
* Time System
|
||||
* */
|
||||
@@ -456,9 +472,7 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* Get the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
* @returns {TimeConductorBounds} The current bounds of the time conductor.
|
||||
*/
|
||||
getBounds() {
|
||||
//Return a copy to prevent direct mutation of time conductor bounds.
|
||||
@@ -469,12 +483,8 @@ class TimeContext extends EventEmitter {
|
||||
* Set the start and end time of the time conductor. Basic validation
|
||||
* of bounds is performed.
|
||||
*
|
||||
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
|
||||
* @throws {Error} Validation error
|
||||
* @fires module:openmct.TimeAPI~bounds
|
||||
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method bounds
|
||||
* @param {TimeConductorBounds} newBounds The new bounds to set.
|
||||
* @throws {Error} Validation error if bounds are invalid
|
||||
*/
|
||||
setBounds(newBounds) {
|
||||
const validationResult = this.validateBounds(newBounds);
|
||||
@@ -487,7 +497,6 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* The start time, end time, or both have been updated.
|
||||
* @event bounds
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {TimeConductorBounds} bounds The newly updated bounds
|
||||
* @property {boolean} [tick] `true` if the bounds update was due to
|
||||
* a "tick" event (i.e. was an automatic update), false otherwise.
|
||||
@@ -498,7 +507,7 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Get the active clock.
|
||||
* @return {Clock} the currently active clock;
|
||||
* @return {Clock|undefined} the currently active clock; undefined if in fixed mode.
|
||||
*/
|
||||
getClock() {
|
||||
return this.activeClock;
|
||||
@@ -509,9 +518,7 @@ class TimeContext extends EventEmitter {
|
||||
* and the currently ticking will begin.
|
||||
* Offsets from 'now', if provided, will be used to set realtime mode offsets
|
||||
*
|
||||
* @param {Clock || string} keyOrClock The clock to activate, or its key
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Clock} the currently active clock;
|
||||
* @param {string|Clock} keyOrClock The clock to activate, or its key
|
||||
*/
|
||||
setClock(keyOrClock) {
|
||||
let clock;
|
||||
@@ -540,7 +547,7 @@ class TimeContext extends EventEmitter {
|
||||
* The active clock has changed.
|
||||
* @event clock
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Clock} clock The newly activated clock, or undefined
|
||||
* @property {TimeContext} clock The newly activated clock, or undefined
|
||||
* if the system is no longer following a clock source
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
|
||||
@@ -549,7 +556,7 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Get the current mode.
|
||||
* @return {Mode} the current mode;
|
||||
* @return {Mode} the current mode
|
||||
*/
|
||||
getMode() {
|
||||
return this.mode;
|
||||
@@ -559,9 +566,9 @@ class TimeContext extends EventEmitter {
|
||||
* Set the mode to either fixed or realtime.
|
||||
*
|
||||
* @param {Mode} mode The mode to activate
|
||||
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
|
||||
* @param {TimeConductorBounds|ClockOffsets} offsetsOrBounds A time window of a fixed width
|
||||
* @fires module:openmct.TimeAPI~clock
|
||||
* @return {Mode} the currently active mode;
|
||||
* @return {Mode | undefined} the currently active mode
|
||||
*/
|
||||
setMode(mode, offsetsOrBounds) {
|
||||
if (!mode) {
|
||||
@@ -577,7 +584,6 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* The active mode has changed.
|
||||
* @event modeChanged
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {Mode} mode The newly activated mode
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
|
||||
@@ -610,18 +616,15 @@ class TimeContext extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Get the currently applied clock offsets.
|
||||
* @returns {ClockOffsets}
|
||||
* @returns {ClockOffsets} The current clock offsets.
|
||||
*/
|
||||
getClockOffsets() {
|
||||
return this.offsets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the currently applied clock offsets. If no parameter is provided,
|
||||
* the current value will be returned. If provided, the new value will be
|
||||
* used as the new clock offsets.
|
||||
* @param {ClockOffsets} offsets
|
||||
* @returns {ClockOffsets}
|
||||
* Set the currently applied clock offsets.
|
||||
* @param {ClockOffsets} offsets The new clock offsets to set.
|
||||
*/
|
||||
setClockOffsets(offsets) {
|
||||
const validationResult = this.validateOffsets(offsets);
|
||||
@@ -642,13 +645,20 @@ class TimeContext extends EventEmitter {
|
||||
/**
|
||||
* Event that is triggered when clock offsets change.
|
||||
* @event clockOffsets
|
||||
* @memberof module:openmct.TimeAPI~
|
||||
* @property {ClockOffsets} clockOffsets The newly activated clock
|
||||
* offsets.
|
||||
*/
|
||||
this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.#copy(offsets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a warning to the console when a deprecated method is used. Limits
|
||||
* the number of times a warning is printed per unique method and newMethod
|
||||
* combination.
|
||||
* @param {string} method the deprecated method
|
||||
* @param {string} [newMethod] the new method to use instead
|
||||
* @returns
|
||||
*/
|
||||
#warnMethodDeprecated(method, newMethod) {
|
||||
const MAX_CALLS = 1; // Only warn once per unique method and newMethod combination
|
||||
|
||||
@@ -673,6 +683,11 @@ class TimeContext extends EventEmitter {
|
||||
console.warn(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy an object.
|
||||
* @param {object} object The object to copy
|
||||
* @returns {object} The copied object
|
||||
*/
|
||||
#copy(object) {
|
||||
return JSON.parse(JSON.stringify(object));
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ export default {
|
||||
|
||||
this.openmct.time.on('timeSystem', this.updateTimeSystem);
|
||||
|
||||
this.timestampKey = this.openmct.time.timeSystem().key;
|
||||
this.timestampKey = this.openmct.time.getTimeSystem().key;
|
||||
|
||||
this.valueMetadata = undefined;
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
TIME_EVENTS.forEach((event) => {
|
||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
this.openmct.time.on('bounds', this.updateBounds);
|
||||
this.openmct.time.on('boundsChanged', this.updateBounds);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -73,7 +73,7 @@ export default class URLTimeSettingsSynchronizer {
|
||||
TIME_EVENTS.forEach((event) => {
|
||||
this.openmct.time.off(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
this.openmct.time.off('bounds', this.updateBounds);
|
||||
this.openmct.time.off('boundsChanged', this.updateBounds);
|
||||
}
|
||||
|
||||
updateTimeSettings() {
|
||||
|
||||
@@ -115,11 +115,11 @@ export default {
|
||||
this.followTimeContext();
|
||||
},
|
||||
followTimeContext() {
|
||||
this.timeContext.on('bounds', this.refreshData);
|
||||
this.timeContext.on('boundsChanged', this.refreshData);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.refreshData);
|
||||
this.timeContext.off('boundsChanged', this.refreshData);
|
||||
}
|
||||
},
|
||||
addToComposition(telemetryObject) {
|
||||
@@ -253,7 +253,7 @@ export default {
|
||||
};
|
||||
},
|
||||
getOptions() {
|
||||
const { start, end } = this.timeContext.bounds();
|
||||
const { start, end } = this.timeContext.getBounds();
|
||||
|
||||
return {
|
||||
end,
|
||||
@@ -372,13 +372,13 @@ export default {
|
||||
this.setTrace(key, telemetryObject.name, axisMetadata, xValues, yValues);
|
||||
},
|
||||
isDataInTimeRange(datum, key, telemetryObject) {
|
||||
const timeSystemKey = this.timeContext.timeSystem().key;
|
||||
const timeSystemKey = this.timeContext.getTimeSystem().key;
|
||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
let metadataValue = metadata.value(timeSystemKey) || { key: timeSystemKey };
|
||||
|
||||
let currentTimestamp = this.parse(key, metadataValue.key, datum);
|
||||
|
||||
return currentTimestamp && this.timeContext.bounds().end >= currentTimestamp;
|
||||
return currentTimestamp && this.timeContext.getBounds().end >= currentTimestamp;
|
||||
},
|
||||
format(telemetryObjectKey, metadataKey, data) {
|
||||
const formats = this.telemetryObjectFormats[telemetryObjectKey];
|
||||
|
||||
@@ -105,11 +105,11 @@ export default {
|
||||
this.followTimeContext();
|
||||
},
|
||||
followTimeContext() {
|
||||
this.timeContext.on('bounds', this.reloadTelemetryOnBoundsChange);
|
||||
this.timeContext.on('boundsChanged', this.reloadTelemetryOnBoundsChange);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.reloadTelemetryOnBoundsChange);
|
||||
this.timeContext.off('boundsChanged', this.reloadTelemetryOnBoundsChange);
|
||||
}
|
||||
},
|
||||
addToComposition(telemetryObject) {
|
||||
@@ -306,7 +306,7 @@ export default {
|
||||
this.trace = [trace];
|
||||
},
|
||||
getTimestampForDatum(datum, key, telemetryObject) {
|
||||
const timeSystemKey = this.timeContext.timeSystem().key;
|
||||
const timeSystemKey = this.timeContext.getTimeSystem().key;
|
||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
let metadataValue = metadata.value(timeSystemKey) || { format: timeSystemKey };
|
||||
|
||||
@@ -327,7 +327,7 @@ export default {
|
||||
return formats[metadataKey].parse(datum);
|
||||
},
|
||||
getOptions() {
|
||||
const { start, end } = this.timeContext.bounds();
|
||||
const { start, end } = this.timeContext.getBounds();
|
||||
|
||||
return {
|
||||
end,
|
||||
|
||||
@@ -245,7 +245,7 @@ export default class Condition extends EventEmitter {
|
||||
latestTimestamp,
|
||||
updatedCriterion.data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
this.openmct.time.getTimeSystem()
|
||||
);
|
||||
this.conditionManager.updateCurrentCondition(latestTimestamp);
|
||||
}
|
||||
@@ -309,7 +309,7 @@ export default class Condition extends EventEmitter {
|
||||
latestTimestamp,
|
||||
data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
this.openmct.time.getTimeSystem()
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
{},
|
||||
{},
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
this.openmct.time.getTimeSystem()
|
||||
);
|
||||
this.updateConditionResults({ id: id });
|
||||
this.updateCurrentCondition(latestTimestamp);
|
||||
@@ -383,7 +383,7 @@ export default class ConditionManager extends EventEmitter {
|
||||
latestTimestamp,
|
||||
data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
this.openmct.time.getTimeSystem()
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
});
|
||||
this.initialize(styleConfigurationWithNoSelection);
|
||||
if (styleConfiguration.conditionSetIdentifier) {
|
||||
this.openmct.time.on('bounds', this.refreshData);
|
||||
this.openmct.time.on('boundsChanged', this.refreshData);
|
||||
this.subscribeToConditionSet();
|
||||
} else {
|
||||
this.applyStaticStyle();
|
||||
@@ -216,7 +216,7 @@ export default class StyleRuleManager extends EventEmitter {
|
||||
}
|
||||
|
||||
if (!skipEventListeners) {
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.openmct.time.off('boundsChanged', this.refreshData);
|
||||
this.openmct.editor.off('isEditing', this.toggleSubscription);
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
return Promise.all(telemetryRequests).then((telemetryRequestsResults) => {
|
||||
let latestTimestamp;
|
||||
const timeSystems = this.openmct.time.getAllTimeSystems();
|
||||
const timeSystem = this.openmct.time.timeSystem();
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
|
||||
telemetryRequestsResults.forEach((results, index) => {
|
||||
const latestDatum =
|
||||
|
||||
@@ -280,7 +280,7 @@ export default {
|
||||
await this.$nextTick();
|
||||
},
|
||||
formattedValueForCopy() {
|
||||
const timeFormatterKey = this.openmct.time.timeSystem().key;
|
||||
const timeFormatterKey = this.openmct.time.getTimeSystem().key;
|
||||
const timeFormatter = this.formats[timeFormatterKey];
|
||||
const unit = this.unit ? ` ${this.unit}` : '';
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||
spyOn(openmct.time, 'getBounds').and.returnValue({
|
||||
start: 1000,
|
||||
end: 5000
|
||||
});
|
||||
@@ -306,7 +306,7 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||
spyOn(openmct.time, 'getBounds').and.returnValue({
|
||||
start: 1000,
|
||||
end: 5000
|
||||
});
|
||||
@@ -448,7 +448,7 @@ describe('Gauge plugin', () => {
|
||||
});
|
||||
spyOn(openmct.telemetry, 'getLimits').and.returnValue({ limits: () => Promise.resolve() });
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||
spyOn(openmct.time, 'getBounds').and.returnValue({
|
||||
start: 1000,
|
||||
end: 5000
|
||||
});
|
||||
@@ -763,7 +763,7 @@ describe('Gauge plugin', () => {
|
||||
})
|
||||
});
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([randomValue]));
|
||||
spyOn(openmct.time, 'bounds').and.returnValue({
|
||||
spyOn(openmct.time, 'getBounds').and.returnValue({
|
||||
start: 1000,
|
||||
end: 5000
|
||||
});
|
||||
|
||||
@@ -363,7 +363,7 @@ export default {
|
||||
rangeLow: gaugeController.min,
|
||||
gaugeType: gaugeController.gaugeType,
|
||||
showUnits: gaugeController.showUnits,
|
||||
activeTimeSystem: this.openmct.time.timeSystem(),
|
||||
activeTimeSystem: this.openmct.time.getTimeSystem(),
|
||||
units: ''
|
||||
};
|
||||
},
|
||||
@@ -545,7 +545,7 @@ export default {
|
||||
|
||||
this.composition.load();
|
||||
|
||||
this.openmct.time.on('bounds', this.refreshData);
|
||||
this.openmct.time.on('boundsChanged', this.refreshData);
|
||||
this.openmct.time.on('timeSystem', this.setTimeSystem);
|
||||
|
||||
this.setupClockChangedEvent((domainObject) => {
|
||||
@@ -561,7 +561,7 @@ export default {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.openmct.time.off('boundsChanged', this.refreshData);
|
||||
this.openmct.time.off('timeSystem', this.setTimeSystem);
|
||||
},
|
||||
methods: {
|
||||
@@ -726,7 +726,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const { start, end } = this.openmct.time.bounds();
|
||||
const { start, end } = this.openmct.time.getBounds();
|
||||
const parsedValue = this.timeFormatter.parse(this.datum);
|
||||
|
||||
const beforeStartOfBounds = parsedValue < start;
|
||||
|
||||
@@ -49,7 +49,7 @@ export default {
|
||||
mixins: [imageryData],
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
this.metadata = {};
|
||||
this.requestCount = 0;
|
||||
|
||||
@@ -109,12 +109,12 @@ export default {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on('timeSystem', this.setScaleAndPlotImagery);
|
||||
this.timeContext.on('bounds', this.updateViewBounds);
|
||||
this.timeContext.on('boundsChanged', this.updateViewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('timeSystem', this.setScaleAndPlotImagery);
|
||||
this.timeContext.off('bounds', this.updateViewBounds);
|
||||
this.timeContext.off('boundsChanged', this.updateViewBounds);
|
||||
}
|
||||
},
|
||||
expand(imageTimestamp) {
|
||||
@@ -148,10 +148,10 @@ export default {
|
||||
return clientWidth;
|
||||
},
|
||||
updateViewBounds(bounds, isTick) {
|
||||
this.viewBounds = this.timeContext.bounds();
|
||||
this.viewBounds = this.timeContext.getBounds();
|
||||
|
||||
if (this.timeSystem === undefined) {
|
||||
this.timeSystem = this.timeContext.timeSystem();
|
||||
this.timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
this.setScaleAndPlotImagery(this.timeSystem, !isTick);
|
||||
@@ -216,7 +216,7 @@ export default {
|
||||
}
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
timeSystem = this.timeContext.timeSystem();
|
||||
timeSystem = this.timeContext.getTimeSystem();
|
||||
}
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
|
||||
@@ -44,7 +44,7 @@ export default class RelatedTelemetry {
|
||||
this.keys = telemetryKeys;
|
||||
|
||||
this._timeFormatter = undefined;
|
||||
this._timeSystemChange(this.timeContext.timeSystem());
|
||||
this._timeSystemChange(this.timeContext.getTimeSystem());
|
||||
|
||||
// grab related telemetry metadata
|
||||
for (let key of this.keys) {
|
||||
@@ -110,10 +110,10 @@ export default class RelatedTelemetry {
|
||||
// and set bounds.
|
||||
ephemeralContext.resetContext();
|
||||
const newBounds = {
|
||||
start: this.timeContext.bounds().start,
|
||||
start: this.timeContext.getBounds().start,
|
||||
end: this._parseTime(datum)
|
||||
};
|
||||
ephemeralContext.bounds(newBounds);
|
||||
ephemeralContext.setBounds(newBounds);
|
||||
|
||||
const options = {
|
||||
start: newBounds.start,
|
||||
|
||||
@@ -171,7 +171,7 @@ export default {
|
||||
this.bounds = bounds; // setting bounds for ImageryView watcher
|
||||
},
|
||||
timeSystemChanged() {
|
||||
this.timeSystem = this.timeContext.timeSystem();
|
||||
this.timeSystem = this.timeContext.getTimeSystem();
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.durationFormatter = this.getFormatter(
|
||||
|
||||
@@ -144,24 +144,132 @@ export default class ImportAsJSONAction {
|
||||
|
||||
return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences]));
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} tree
|
||||
* @param {string} namespace
|
||||
* @returns {Object}
|
||||
*/
|
||||
_generateNewIdentifiers(tree, newNamespace) {
|
||||
// For each domain object in the file, generate new ID, replace in tree
|
||||
Object.keys(tree.openmct).forEach((domainObjectId) => {
|
||||
const oldId = parseKeyString(domainObjectId);
|
||||
|
||||
/**
|
||||
* Generates a map of old IDs to new IDs for efficient lookup during tree walking.
|
||||
* This function considers cases where original namespaces are blank and updates those IDs as well.
|
||||
*
|
||||
* @param {Object} tree - The object tree containing the old IDs.
|
||||
* @param {string} newNamespace - The namespace for the new IDs.
|
||||
* @returns {Object} A map of old IDs to new IDs.
|
||||
*/
|
||||
_generateIdMap(tree, newNamespace) {
|
||||
const idMap = {};
|
||||
const keys = Object.keys(tree.openmct);
|
||||
|
||||
for (const oldIdKey of keys) {
|
||||
const oldId = parseKeyString(oldIdKey);
|
||||
const newId = {
|
||||
namespace: newNamespace,
|
||||
key: uuid()
|
||||
};
|
||||
tree = this._rewriteId(oldId, newId, tree);
|
||||
}, this);
|
||||
const newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
||||
|
||||
// Update the map with the old and new ID key strings.
|
||||
idMap[oldIdKey] = newIdKeyString;
|
||||
|
||||
// If the old namespace is blank, also map the non-namespaced ID.
|
||||
if (!oldId.namespace) {
|
||||
const nonNamespacedOldIdKey = oldId.key;
|
||||
idMap[nonNamespacedOldIdKey] = newIdKeyString;
|
||||
}
|
||||
}
|
||||
|
||||
return idMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks through the object tree and updates IDs according to the provided ID map.
|
||||
* @param {Object} obj - The current object being visited in the tree.
|
||||
* @param {Object} idMap - A map of old IDs to new IDs for rewriting.
|
||||
* @param {Object} importDialog - Optional progress dialog for import.
|
||||
* @returns {Promise<Object>} The object with updated IDs.
|
||||
*/
|
||||
async _walkAndRewriteIds(obj, idMap, importDialog) {
|
||||
// How many rewrites to do before yielding to the event loop
|
||||
const UI_UPDATE_INTERVAL = 300;
|
||||
// The percentage of the progress dialog to allocate to rewriting IDs
|
||||
const PERCENT_OF_DIALOG = 80;
|
||||
if (obj === null || obj === undefined) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (typeof obj === 'string') {
|
||||
const possibleId = idMap[obj];
|
||||
if (possibleId) {
|
||||
return possibleId;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.hasOwn(obj, 'key') && Object.hasOwn(obj, 'namespace')) {
|
||||
const oldId = this.openmct.objects.makeKeyString(obj);
|
||||
const possibleId = idMap[oldId];
|
||||
|
||||
if (possibleId) {
|
||||
const newIdParts = possibleId.split(':');
|
||||
if (newIdParts.length >= 2) {
|
||||
// new ID is namespaced, so update both the namespace and key
|
||||
obj.namespace = newIdParts[0];
|
||||
obj.key = newIdParts[1];
|
||||
} else {
|
||||
// old ID was not namespaced, so update the key only
|
||||
obj.namespace = '';
|
||||
obj.key = newIdParts[0];
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
obj[i] = await this._walkAndRewriteIds(obj[i], idMap); // Process each item in the array
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
const newObj = {};
|
||||
|
||||
const keys = Object.keys(obj);
|
||||
let processedCount = 0;
|
||||
for (const key of keys) {
|
||||
const value = obj[key];
|
||||
const possibleId = idMap[key];
|
||||
const newKey = possibleId || key;
|
||||
|
||||
newObj[newKey] = await this._walkAndRewriteIds(value, idMap);
|
||||
|
||||
// Optionally update the importDialog here, after each property has been processed
|
||||
if (importDialog) {
|
||||
processedCount++;
|
||||
if (processedCount % UI_UPDATE_INTERVAL === 0) {
|
||||
// yield to the event loop to allow the UI to update
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
const percentPersisted = Math.ceil(PERCENT_OF_DIALOG * (processedCount / keys.length));
|
||||
const message = `Rewriting ${processedCount} of ${keys.length} imported objects.`;
|
||||
importDialog.updateProgress(percentPersisted, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
||||
// Return the input as-is for types that are not objects, strings, or arrays
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} tree
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async _generateNewIdentifiers(tree, newNamespace, importDialog) {
|
||||
const idMap = this._generateIdMap(tree, newNamespace);
|
||||
tree.rootId = idMap[tree.rootId];
|
||||
tree.openmct = await this._walkAndRewriteIds(tree.openmct, idMap, importDialog);
|
||||
return tree;
|
||||
}
|
||||
/**
|
||||
@@ -170,9 +278,16 @@ export default class ImportAsJSONAction {
|
||||
* @param {Object} objTree
|
||||
*/
|
||||
async _importObjectTree(domainObject, objTree) {
|
||||
// make rewriting objects IDs 80% of the progress bar
|
||||
const importDialog = this.openmct.overlays.progressDialog({
|
||||
progressPerc: 0,
|
||||
message: `Importing ${Object.keys(objTree.openmct).length} objects`,
|
||||
iconClass: 'info',
|
||||
title: 'Importing'
|
||||
});
|
||||
const objectsToCreate = [];
|
||||
const namespace = domainObject.identifier.namespace;
|
||||
const tree = this._generateNewIdentifiers(objTree, namespace);
|
||||
const tree = await this._generateNewIdentifiers(objTree, namespace, importDialog);
|
||||
const rootId = tree.rootId;
|
||||
|
||||
const rootObj = tree.openmct[rootId];
|
||||
@@ -182,11 +297,24 @@ export default class ImportAsJSONAction {
|
||||
this._deepInstantiate(rootObj, tree.openmct, [], objectsToCreate);
|
||||
|
||||
try {
|
||||
await Promise.all(objectsToCreate.map(this._instantiate, this));
|
||||
let persistedObjects = 0;
|
||||
// make saving objects objects 20% of the progress bar
|
||||
await Promise.all(
|
||||
objectsToCreate.map(async (objectToCreate) => {
|
||||
persistedObjects++;
|
||||
const percentPersisted =
|
||||
Math.ceil(20 * (persistedObjects / objectsToCreate.length)) + 80;
|
||||
const message = `Saving ${persistedObjects} of ${objectsToCreate.length} imported objects.`;
|
||||
importDialog.updateProgress(percentPersisted, message);
|
||||
await this._instantiate(objectToCreate);
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
this.openmct.notifications.error('Error saving objects');
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
importDialog.dismiss();
|
||||
}
|
||||
|
||||
const compositionCollection = this.openmct.composition.get(domainObject);
|
||||
@@ -194,7 +322,8 @@ export default class ImportAsJSONAction {
|
||||
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
|
||||
compositionCollection.add(rootObj);
|
||||
} else {
|
||||
const dialog = this.openmct.overlays.dialog({
|
||||
importDialog.dismiss();
|
||||
const cannotImportDialog = this.openmct.overlays.dialog({
|
||||
iconClass: 'alert',
|
||||
message: "We're sorry, but you cannot import that object type into this object.",
|
||||
buttons: [
|
||||
@@ -202,7 +331,7 @@ export default class ImportAsJSONAction {
|
||||
label: 'Ok',
|
||||
emphasis: true,
|
||||
callback: function () {
|
||||
dialog.dismiss();
|
||||
cannotImportDialog.dismiss();
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -217,43 +346,7 @@ export default class ImportAsJSONAction {
|
||||
_instantiate(model) {
|
||||
return this.openmct.objects.save(model);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} oldId
|
||||
* @param {Object} newId
|
||||
* @param {Object} tree
|
||||
* @returns {Object}
|
||||
*/
|
||||
_rewriteId(oldId, newId, tree) {
|
||||
let newIdKeyString = this.openmct.objects.makeKeyString(newId);
|
||||
let oldIdKeyString = this.openmct.objects.makeKeyString(oldId);
|
||||
const newTreeString = JSON.stringify(tree).replace(
|
||||
new RegExp(oldIdKeyString, 'g'),
|
||||
newIdKeyString
|
||||
);
|
||||
const newTree = JSON.parse(newTreeString, (key, value) => {
|
||||
if (
|
||||
value !== undefined &&
|
||||
value !== null &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'key') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'namespace')
|
||||
) {
|
||||
// first check if key is messed up from regex and contains a colon
|
||||
// if it does, repair it
|
||||
if (value.key.includes(':')) {
|
||||
const splitKey = value.key.split(':');
|
||||
value.key = splitKey[1];
|
||||
value.namespace = splitKey[0];
|
||||
}
|
||||
// now check if we need to replace the id
|
||||
if (value.key === oldId.key && value.namespace === oldId.namespace) {
|
||||
return newId;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
return newTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} domainObject
|
||||
|
||||
@@ -111,7 +111,6 @@ describe('The import JSON action', function () {
|
||||
});
|
||||
|
||||
it('protects against prototype pollution', (done) => {
|
||||
spyOn(console, 'warn');
|
||||
spyOn(openmct.forms, 'showForm').and.callFake(returnResponseWithPrototypePollution);
|
||||
|
||||
unObserve = openmct.objects.observe(folderObject, '*', callback);
|
||||
@@ -123,8 +122,6 @@ describe('The import JSON action', function () {
|
||||
Object.prototype.hasOwnProperty.call(newObject, '__proto__') ||
|
||||
Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(newObject), 'toString');
|
||||
|
||||
// warning from openmct.objects.get
|
||||
expect(console.warn).not.toHaveBeenCalled();
|
||||
expect(hasPollutedProto).toBeFalse();
|
||||
|
||||
done();
|
||||
@@ -192,6 +189,12 @@ describe('The import JSON action', function () {
|
||||
type: 'folder'
|
||||
};
|
||||
spyOn(openmct.objects, 'save').and.callFake((model) => Promise.resolve(model));
|
||||
spyOn(openmct.overlays, 'progressDialog').and.callFake(() => {
|
||||
return {
|
||||
updateProgress: () => {},
|
||||
dismiss: () => {}
|
||||
};
|
||||
});
|
||||
try {
|
||||
await importFromJSONAction.onSave(targetDomainObject, {
|
||||
selectFile: { body: JSON.stringify(incomingObject) }
|
||||
|
||||
@@ -680,7 +680,7 @@ export default {
|
||||
} else if (domainObjectData) {
|
||||
// plain domain object
|
||||
const objectPath = JSON.parse(domainObjectData);
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const snapshotMeta = {
|
||||
bounds,
|
||||
link: null,
|
||||
|
||||
@@ -275,10 +275,10 @@ export default {
|
||||
}
|
||||
const hash = this.embed.historicLink;
|
||||
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const isTimeBoundChanged =
|
||||
this.embed.bounds.start !== bounds.start || this.embed.bounds.end !== bounds.end;
|
||||
const isFixedTimespanMode = !this.openmct.time.clock();
|
||||
const isFixedTimespanMode = this.openmct.time.isFixed();
|
||||
|
||||
let message = '';
|
||||
if (isTimeBoundChanged) {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
@drop.capture="cancelEditMode"
|
||||
@drop.prevent="dropOnEntry"
|
||||
@click="selectAndEmitEntry($event, entry)"
|
||||
@paste="addImageFromPaste"
|
||||
@paste="handlePaste"
|
||||
>
|
||||
<div class="c-ne__time-and-content">
|
||||
<div class="c-ne__time-and-creator-and-delete">
|
||||
@@ -368,8 +368,30 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handlePaste(event) {
|
||||
const clipboardItems = Array.from(
|
||||
(event.clipboardData || event.originalEvent.clipboardData).items
|
||||
);
|
||||
const hasClipboardText = clipboardItems.some(
|
||||
(clipboardItem) => clipboardItem.kind === 'string'
|
||||
);
|
||||
const clipboardImages = clipboardItems.filter(
|
||||
(clipboardItem) => clipboardItem.kind === 'file' && clipboardItem.type.includes('image')
|
||||
);
|
||||
const hasClipboardImages = clipboardImages?.length > 0;
|
||||
|
||||
if (hasClipboardImages) {
|
||||
if (hasClipboardText) {
|
||||
console.warn('Image and text kinds found in paste. Only processing images.');
|
||||
}
|
||||
|
||||
this.addImageFromPaste(clipboardImages, event);
|
||||
} else if (hasClipboardText) {
|
||||
this.addTextFromPaste(event);
|
||||
}
|
||||
},
|
||||
async addNewEmbed(objectPath) {
|
||||
const bounds = this.openmct.time.bounds();
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const snapshotMeta = {
|
||||
bounds,
|
||||
link: null,
|
||||
@@ -384,32 +406,34 @@ export default {
|
||||
|
||||
this.manageEmbedLayout();
|
||||
},
|
||||
async addImageFromPaste(event) {
|
||||
const clipboardItems = Array.from(
|
||||
(event.clipboardData || event.originalEvent.clipboardData).items
|
||||
);
|
||||
const hasImage = clipboardItems.some(
|
||||
(clipboardItem) => clipboardItem.type.includes('image') && clipboardItem.kind === 'file'
|
||||
);
|
||||
// If the clipboard contained an image, prevent the paste event from reaching the textarea.
|
||||
if (hasImage) {
|
||||
addTextFromPaste(event) {
|
||||
if (!this.editMode) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
async addImageFromPaste(clipboardImages, event) {
|
||||
event?.preventDefault();
|
||||
let updated = false;
|
||||
|
||||
await Promise.all(
|
||||
Array.from(clipboardItems).map(async (clipboardItem) => {
|
||||
const isImage = clipboardItem.type.includes('image') && clipboardItem.kind === 'file';
|
||||
if (isImage) {
|
||||
const imageFile = clipboardItem.getAsFile();
|
||||
const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name);
|
||||
if (!this.entry.embeds) {
|
||||
this.entry.embeds = [];
|
||||
}
|
||||
this.entry.embeds.push(imageEmbed);
|
||||
Array.from(clipboardImages).map(async (clipboardImage) => {
|
||||
const imageFile = clipboardImage.getAsFile();
|
||||
const imageEmbed = await createNewImageEmbed(imageFile, this.openmct, imageFile?.name);
|
||||
|
||||
if (!this.entry.embeds) {
|
||||
this.entry.embeds = [];
|
||||
}
|
||||
|
||||
this.entry.embeds.push(imageEmbed);
|
||||
|
||||
updated = true;
|
||||
})
|
||||
);
|
||||
this.manageEmbedLayout();
|
||||
this.timestampAndUpdate();
|
||||
|
||||
if (updated) {
|
||||
this.manageEmbedLayout();
|
||||
this.timestampAndUpdate();
|
||||
}
|
||||
},
|
||||
convertMarkDownToHtml(text = '') {
|
||||
let markDownHtml = this.marked.parse(text, {
|
||||
|
||||
@@ -123,7 +123,7 @@ export default {
|
||||
const objectPath = this.objectPath || this.openmct.router.path;
|
||||
const link = this.isPreview ? this.getPreviewObjectLink() : window.location.hash;
|
||||
const snapshotMeta = {
|
||||
bounds: this.openmct.time.bounds(),
|
||||
bounds: this.openmct.time.getBounds(),
|
||||
link,
|
||||
objectPath,
|
||||
openmct: this.openmct
|
||||
|
||||
@@ -140,7 +140,7 @@ export function createNewImageEmbed(image, openmct, imageName = '') {
|
||||
};
|
||||
|
||||
const embedMetaData = {
|
||||
bounds: openmct.time.bounds(),
|
||||
bounds: openmct.time.getBounds(),
|
||||
link: null,
|
||||
objectPath: null,
|
||||
openmct,
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class PainterroInstance {
|
||||
this.config.id = this.elementId;
|
||||
this.config.saveHandler = this.saveHandler.bind(this);
|
||||
|
||||
this.painterro = Painterro.default(this.config);
|
||||
this.painterro = Painterro(this.config);
|
||||
}
|
||||
|
||||
save(callback) {
|
||||
|
||||
@@ -710,7 +710,7 @@ class CouchObjectProvider {
|
||||
this.objectQueue[key].pending = true;
|
||||
const queued = this.objectQueue[key].dequeue();
|
||||
let couchDocument = new CouchDocument(key, queued.model);
|
||||
couchDocument.metadata.created = Date.now();
|
||||
|
||||
this.#enqueueForPersistence({
|
||||
key,
|
||||
document: couchDocument
|
||||
|
||||
@@ -196,10 +196,10 @@ export default {
|
||||
this.followTimeContext();
|
||||
},
|
||||
followTimeContext() {
|
||||
this.updateViewBounds(this.timeContext.bounds());
|
||||
this.updateViewBounds(this.timeContext.getBounds());
|
||||
|
||||
this.timeContext.on('timeSystem', this.setScaleAndGenerateActivities);
|
||||
this.timeContext.on('bounds', this.updateViewBounds);
|
||||
this.timeContext.on('boundsChanged', this.updateViewBounds);
|
||||
},
|
||||
loadComposition() {
|
||||
if (this.composition) {
|
||||
@@ -211,7 +211,7 @@ export default {
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('timeSystem', this.setScaleAndGenerateActivities);
|
||||
this.timeContext.off('bounds', this.updateViewBounds);
|
||||
this.timeContext.off('boundsChanged', this.updateViewBounds);
|
||||
}
|
||||
},
|
||||
showReplacePlanDialog(domainObject) {
|
||||
@@ -319,7 +319,7 @@ export default {
|
||||
}
|
||||
|
||||
if (this.timeSystem === null) {
|
||||
this.timeSystem = this.openmct.time.timeSystem();
|
||||
this.timeSystem = this.openmct.time.getTimeSystem();
|
||||
}
|
||||
|
||||
this.setScaleAndGenerateActivities();
|
||||
@@ -344,7 +344,7 @@ export default {
|
||||
}
|
||||
|
||||
if (!timeSystem) {
|
||||
timeSystem = this.openmct.time.timeSystem();
|
||||
timeSystem = this.openmct.time.getTimeSystem();
|
||||
}
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
}
|
||||
},
|
||||
setFormatters() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
this.timeFormatter = this.openmct.telemetry.getValueFormatter({
|
||||
format: timeSystem.timeFormat
|
||||
}).formatter;
|
||||
|
||||
@@ -661,7 +661,7 @@ export default {
|
||||
this.offsetWidth = this.$parent.$refs.plotWrapper.offsetWidth;
|
||||
|
||||
this.startLoading();
|
||||
const bounds = this.timeContext.bounds();
|
||||
const bounds = this.timeContext.getBounds();
|
||||
const options = {
|
||||
size: this.$parent.$refs.plotWrapper.offsetWidth,
|
||||
domain: this.config.xAxis.get('key'),
|
||||
@@ -1860,8 +1860,8 @@ export default {
|
||||
},
|
||||
|
||||
showSynchronizeDialog() {
|
||||
const isLocalClock = this.timeContext.clock();
|
||||
if (isLocalClock !== undefined) {
|
||||
const isFixedTimespanMode = this.timeContext.isFixed();
|
||||
if (!isFixedTimespanMode) {
|
||||
const message = `
|
||||
This action will change the Time Conductor to Fixed Timespan mode with this plot view's current time bounds.
|
||||
Do you want to continue?
|
||||
|
||||
@@ -614,7 +614,7 @@ export default {
|
||||
const yAxisId = series.get('yAxisId') || mainYAxisId;
|
||||
let offset = this.offset[yAxisId];
|
||||
|
||||
return new MCTChartAlarmLineSet(series, this, offset, this.openmct.time.bounds());
|
||||
return new MCTChartAlarmLineSet(series, this, offset, this.openmct.time.getBounds());
|
||||
},
|
||||
pointSetForSeries(series) {
|
||||
const mainYAxisId = this.config.yAxis.get('id');
|
||||
|
||||
@@ -140,7 +140,7 @@ export default class PlotSeries extends Model {
|
||||
//this triggers Model.destroy which in turn triggers destroy methods for other classes.
|
||||
super.destroy();
|
||||
this.stopListening();
|
||||
this.openmct.time.off('bounds', this.updateLimits);
|
||||
this.openmct.time.off('boundsChanged', this.updateLimits);
|
||||
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
@@ -171,7 +171,7 @@ export default class PlotSeries extends Model {
|
||||
this.limitEvaluator = this.openmct.telemetry.limitEvaluator(options.domainObject);
|
||||
this.limitDefinition = this.openmct.telemetry.limitDefinition(options.domainObject);
|
||||
this.limits = [];
|
||||
this.openmct.time.on('bounds', this.updateLimits);
|
||||
this.openmct.time.on('boundsChanged', this.updateLimits);
|
||||
this.removeMutationListener = this.openmct.objects.observe(
|
||||
this.domainObject,
|
||||
'name',
|
||||
|
||||
@@ -93,7 +93,7 @@ export default class XAxisModel extends Model {
|
||||
* @override
|
||||
*/
|
||||
defaultModel(options) {
|
||||
const bounds = options.openmct.time.bounds();
|
||||
const bounds = options.openmct.time.getBounds();
|
||||
const timeSystem = options.openmct.time.getTimeSystem();
|
||||
const format = options.openmct.telemetry.getFormatter(timeSystem.timeFormat);
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ export default class RemoteClock extends DefaultClock {
|
||||
* @private
|
||||
*/
|
||||
_timeSystemChange() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
let timeSystem = this.openmct.time.getTimeSystem();
|
||||
let timeKey = timeSystem.key;
|
||||
let metadataValue = this.metadata.value(timeKey);
|
||||
let timeFormatter = this.openmct.telemetry.getValueFormatter(metadataValue);
|
||||
@@ -155,7 +155,7 @@ export default class RemoteClock extends DefaultClock {
|
||||
#waitForReady() {
|
||||
const waitForInitialTick = (resolve) => {
|
||||
if (this.lastTick > 0) {
|
||||
const offsets = this.openmct.time.clockOffsets();
|
||||
const offsets = this.openmct.time.getClockOffsets();
|
||||
resolve({
|
||||
start: this.lastTick + offsets.start,
|
||||
end: this.lastTick + offsets.end
|
||||
|
||||
@@ -62,7 +62,7 @@ SummaryWidgetEvaluator.prototype.subscribe = function (callback) {
|
||||
}
|
||||
|
||||
const updateCallback = function () {
|
||||
const datum = this.evaluateState(realtimeStates, this.openmct.time.timeSystem().key);
|
||||
const datum = this.evaluateState(realtimeStates, this.openmct.time.getTimeSystem().key);
|
||||
if (datum) {
|
||||
callback(datum);
|
||||
}
|
||||
|
||||
@@ -611,11 +611,11 @@ describe('The Mean Telemetry Provider', function () {
|
||||
}
|
||||
|
||||
function createMockTimeApi() {
|
||||
return jasmine.createSpyObj('timeApi', ['timeSystem']);
|
||||
return jasmine.createSpyObj('timeApi', ['getTimeSystem']);
|
||||
}
|
||||
|
||||
function setTimeSystemTo(timeSystemKey) {
|
||||
mockApi.time.timeSystem.and.returnValue({
|
||||
mockApi.time.getTimeSystem.and.returnValue({
|
||||
key: timeSystemKey
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ TelemetryAverager.prototype.calculateMean = function () {
|
||||
* @private
|
||||
*/
|
||||
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
|
||||
const domainKey = this.timeAPI.timeSystem().key;
|
||||
const domainKey = this.timeAPI.getTimeSystem().key;
|
||||
if (domainKey !== this.domainKey) {
|
||||
this.domainKey = domainKey;
|
||||
this.domainFormatter = this.getFormatter(domainKey);
|
||||
|
||||
@@ -134,7 +134,7 @@ export default class TelemetryTable extends EventEmitter {
|
||||
|
||||
//If no persisted sort order, default to sorting by time system, descending.
|
||||
sortOptions = sortOptions || {
|
||||
key: this.openmct.time.timeSystem().key,
|
||||
key: this.openmct.time.getTimeSystem().key,
|
||||
direction: 'desc'
|
||||
};
|
||||
|
||||
@@ -171,6 +171,10 @@ export default class TelemetryTable extends EventEmitter {
|
||||
|
||||
this.removeTelemetryCollection(keyString);
|
||||
|
||||
let sortOptions = this.configuration.getConfiguration().sortOptions;
|
||||
requestOptions.order =
|
||||
sortOptions?.direction ?? (this.telemetryMode === 'performance' ? 'desc' : 'asc');
|
||||
|
||||
if (this.telemetryMode === 'performance') {
|
||||
requestOptions.size = this.rowLimit;
|
||||
requestOptions.enforceSize = true;
|
||||
|
||||
@@ -40,6 +40,8 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
'configuration',
|
||||
this.objectMutated
|
||||
);
|
||||
|
||||
this.notPersistable = !this.openmct.objects.isPersistable(this.domainObject.identifier);
|
||||
}
|
||||
|
||||
getConfiguration() {
|
||||
@@ -52,14 +54,19 @@ export default class TelemetryTableConfiguration extends EventEmitter {
|
||||
// anything that doesn't have a telemetryMode existed before the change and should
|
||||
// take the properties of any passed in defaults or the defaults from the plugin
|
||||
configuration.telemetryMode = configuration.telemetryMode ?? this.defaultOptions.telemetryMode;
|
||||
configuration.persistModeChange =
|
||||
configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
|
||||
configuration.persistModeChange = this.notPersistable
|
||||
? false
|
||||
: configuration.persistModeChange ?? this.defaultOptions.persistModeChange;
|
||||
configuration.rowLimit = configuration.rowLimit ?? this.defaultOptions.rowLimit;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
updateConfiguration(configuration) {
|
||||
if (this.notPersistable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
|
||||
}
|
||||
|
||||
|
||||
@@ -546,7 +546,7 @@ export default {
|
||||
this.table.tableRows.on('sort', this.throttledUpdateVisibleRows);
|
||||
this.table.tableRows.on('filter', this.throttledUpdateVisibleRows);
|
||||
|
||||
this.openmct.time.on('bounds', this.boundsChanged);
|
||||
this.openmct.time.on('boundsChanged', this.boundsChanged);
|
||||
|
||||
//Default sort
|
||||
this.sortOptions = this.table.tableRows.sortBy();
|
||||
@@ -579,7 +579,7 @@ export default {
|
||||
|
||||
this.table.configuration.off('change', this.updateConfiguration);
|
||||
|
||||
this.openmct.time.off('bounds', this.boundsChanged);
|
||||
this.openmct.time.off('boundsChanged', this.boundsChanged);
|
||||
|
||||
this.table.configuration.destroy();
|
||||
|
||||
|
||||
@@ -141,7 +141,6 @@ export default {
|
||||
data() {
|
||||
const bounds = this.openmct.time.getBounds();
|
||||
const timeSystem = this.openmct.time.getTimeSystem();
|
||||
// const isFixed = this.openmct.time.isFixed();
|
||||
|
||||
return {
|
||||
timeSystem,
|
||||
|
||||
@@ -173,16 +173,16 @@ export default {
|
||||
});
|
||||
},
|
||||
getBoundsForTimeSystem(timeSystem) {
|
||||
const currentBounds = this.timeContext.bounds();
|
||||
const currentBounds = this.timeContext.getBounds();
|
||||
|
||||
//TODO: Some kind of translation via an offset? of current bounds to target timeSystem
|
||||
return currentBounds;
|
||||
},
|
||||
updateViewBounds() {
|
||||
const bounds = this.timeContext.bounds();
|
||||
const bounds = this.timeContext.getBounds();
|
||||
this.updateContentHeight();
|
||||
let currentTimeSystemIndex = this.timeSystems.findIndex(
|
||||
(item) => item.timeSystem.key === this.openmct.time.timeSystem().key
|
||||
(item) => item.timeSystem.key === this.openmct.time.getTimeSystem().key
|
||||
);
|
||||
if (currentTimeSystemIndex > -1) {
|
||||
let currentTimeSystem = {
|
||||
@@ -198,13 +198,13 @@ export default {
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.getTimeSystems();
|
||||
this.updateViewBounds();
|
||||
this.timeContext.on('bounds', this.updateViewBounds);
|
||||
this.timeContext.on('clock', this.updateViewBounds);
|
||||
this.timeContext.on('boundsChanged', this.updateViewBounds);
|
||||
this.timeContext.on('clockChanged', this.updateViewBounds);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.updateViewBounds);
|
||||
this.timeContext.off('clock', this.updateViewBounds);
|
||||
this.timeContext.off('boundsChanged', this.updateViewBounds);
|
||||
this.timeContext.off('clockChanged', this.updateViewBounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ const headerItems = [
|
||||
property: 'start',
|
||||
name: 'Start Time',
|
||||
format: function (value, object, key, openmct, options = {}) {
|
||||
const timeFormat = openmct.time.timeSystem().timeFormat;
|
||||
const timeFormat = openmct.time.getTimeSystem().timeFormat;
|
||||
const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter;
|
||||
if (options.skipDateForToday) {
|
||||
return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS);
|
||||
@@ -112,7 +112,7 @@ const headerItems = [
|
||||
property: 'end',
|
||||
name: 'End Time',
|
||||
format: function (value, object, key, openmct, options = {}) {
|
||||
const timeFormat = openmct.time.timeSystem().timeFormat;
|
||||
const timeFormat = openmct.time.getTimeSystem().timeFormat;
|
||||
const timeFormatter = openmct.telemetry.getValueFormatter({ format: timeFormat }).formatter;
|
||||
if (options.skipDateForToday) {
|
||||
return timeFormatter.format(value, SAME_DAY_PRECISION_SECONDS);
|
||||
@@ -425,14 +425,14 @@ export default {
|
||||
},
|
||||
isActivityInBounds(activity) {
|
||||
const startInBounds =
|
||||
activity.start >= this.timeContext.bounds()?.start &&
|
||||
activity.start <= this.timeContext.bounds()?.end;
|
||||
activity.start >= this.timeContext.getBounds()?.start &&
|
||||
activity.start <= this.timeContext.getBounds()?.end;
|
||||
const endInBounds =
|
||||
activity.end >= this.timeContext.bounds()?.start &&
|
||||
activity.end <= this.timeContext.bounds()?.end;
|
||||
activity.end >= this.timeContext.getBounds()?.start &&
|
||||
activity.end <= this.timeContext.getBounds()?.end;
|
||||
const middleInBounds =
|
||||
activity.start <= this.timeContext.bounds()?.start &&
|
||||
activity.end >= this.timeContext.bounds()?.end;
|
||||
activity.start <= this.timeContext.getBounds()?.start &&
|
||||
activity.end >= this.timeContext.getBounds()?.end;
|
||||
|
||||
return startInBounds || endInBounds || middleInBounds;
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ export default class StalenessUtils {
|
||||
|
||||
shouldUpdateStaleness(stalenessResponse, id) {
|
||||
const stalenessResponseTime = this.parseTime(stalenessResponse);
|
||||
const { start } = this.openmct.time.bounds();
|
||||
const { start } = this.openmct.time.getBounds();
|
||||
const isStalenessInCurrentClock = stalenessResponseTime > start;
|
||||
|
||||
if (stalenessResponseTime > this.lastStalenessResponseTime && isStalenessInCurrentClock) {
|
||||
|
||||
@@ -16,14 +16,22 @@
|
||||
"noImplicitAny": false,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "dist",
|
||||
"outFile": "dist/types/index.d.ts",
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2015",
|
||||
"paths": {
|
||||
// matches the alias in webpack config, so that types for those imports are visible.
|
||||
"@/*": ["src/*"]
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["src/api/**/*.js"],
|
||||
"exclude": ["node_modules", "dist", "**/*Spec.js"]
|
||||
}
|
||||
"include": [
|
||||
"src/api/**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*Spec.js"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user