Compare commits

...

33 Commits

Author SHA1 Message Date
Jesse Mazzella
d750f5949b docs: update README.md 2024-04-12 15:00:07 -07:00
Jesse Mazzella
7d64bfb415 fix: remove usage of deprecated timeAPI methods and add a ton of docs and types 2024-04-12 13:27:34 -07:00
Jesse Mazzella
a453f6d606 chore: add webpack consts to eslintrc 2024-04-12 13:27:34 -07:00
Jesse Mazzella
efe3ef3ea9 fix: types for composables 2024-04-12 13:27:34 -07:00
Jesse Mazzella
692c9ee848 fix: parameter type 2024-04-12 13:27:34 -07:00
Jesse Mazzella
153c44d9c6 fix: return type 2024-04-12 13:27:34 -07:00
Jesse Mazzella
7b8961594a chore: remove EventEmitter webpack alias as it hide types 2024-04-12 13:27:34 -07:00
Jesse Mazzella
71493bbc25 fix: unbreak the linting 2024-04-12 13:27:34 -07:00
Jesse Mazzella
f4b27bb7bd docs: improved types 2024-04-12 13:27:34 -07:00
Jesse Mazzella
fe424e1e52 docs: improved types for main entry 2024-04-12 13:27:34 -07:00
Jesse Mazzella
d692447db5 docs: more types 2024-04-12 13:27:34 -07:00
Jesse Mazzella
844d28f74c docs: fix ObjectAPI types and docs 2024-04-12 13:27:34 -07:00
Jesse Mazzella
780557eebd docs: types for StatusAPI 2024-04-12 13:27:34 -07:00
Jesse Mazzella
24b6aa5da5 docs: types for TypeRegistry 2024-04-12 13:27:34 -07:00
Jesse Mazzella
a5148b8484 docs: fix types for eventHelpers 2024-04-12 13:27:34 -07:00
Jesse Mazzella
d347f3b26e docs: fix type imports 2024-04-12 13:27:34 -07:00
Jesse Mazzella
a859e7ee18 docs: fix type imports in openmct.js 2024-04-12 13:27:34 -07:00
Andrew Henry
b18aa48141 Revert "Handle the case where the pasted data is not an image" (#7668)
Revert "Handle the case where the pasted data is not an image (#7628)"

This reverts commit d33da65dae.
2024-04-04 15:03:45 -07:00
Shefali Joshi
d33da65dae Handle the case where the pasted data is not an image (#7628)
* Handle the case where the pasted data is not an image or it is image AND text
* Change method name for paste handling

---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2024-04-04 21:30:12 +00:00
David Tsay
e3adeb6a75 Do not add unused created attribute to metadata of couch documents on create (#7656)
this isn't used anywhere

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2024-04-04 13:33:36 -07:00
Jamie V
de3dad02b5 [Telemetry Tables] Don't mutate configuration if object is not able to be persisted (#7626)
* source maps

* do not persist if obj is not persistable

* nope

* prevent mutation of configuration

* static roots are read only by nature

* helps to use functions correctly

* update persistModeChange logic

* remove debug

* remove unnecessary change
2024-04-04 00:38:59 +00:00
Jesse Mazzella
311ad0b87a fix(e2e): specify .nyc_output path as custom config setting (#7658)
* fix: specify .nyc_output path as custom config setting

* fix: coverage for mobile suite

* fix: pathing in playwright configs
2024-04-01 18:29:47 +00:00
Jesse Mazzella
f98eb31956 fix: move file to correct folder (#7652) 2024-03-28 17:32:46 -07:00
Jamie V
1671a585fb Mct7636 (#7645)
* setting order for sort to descending if in performance mode and no sort set

---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2024-03-28 17:32:03 -07:00
Jesse Mazzella
a3fb84ad43 chore: remove type: module, create openmct-e2e subpackage (#7590)
* fix: remove mystery webpack code

* fix: remove type:module and specify exports

- we aren't a module... yet

* fix: rename webpack*.js to webpack*.mjs so we can use import/export. fix imports

* fix: exports format

* fix: woops, need to add `start` script back

* chore: split e2e into its own module

* fix: use normal Painterro import

* fix: update e2e pathing

* fix: copy over helper functions

* chore: specify `cwd` for playwright configs so that openmct npm commands work as intended in any environment

* chore: add pretest script to e2e package.json

* chore: don't package e2e

* refactor: tidy up webpack common config

* chore: compile types to a single file

* chore: fix visual test npm scripts

* chore: fix import pathing

* chore: define package exports, move test specific dependencies to the subpackage

* chore: export test framework from openmct-e2e

* chore: export baseFixtures also

* chore: let `openmct` and `openmct-e2e` share `node_modules/`

* chore: use `--workspace`, remove pretest script

* Revert "fix: remove mystery webpack code"

This reverts commit eb14d52569ffa27ab1a090b883694f4707b59cd0.

* chore: update package-lock

* chore: add `.npmignore`

* fix: *js -> mjs
2024-03-28 14:49:00 -07:00
dependabot[bot]
a5c6b141a6 chore(deps-dev): bump express from 4.18.3 to 4.19.2 (#7646)
Bumps [express](https://github.com/expressjs/express) from 4.18.3 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.3...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 13:12:48 -07:00
Jesse Mazzella
46823ec225 chore(gha): run prcop workflow on milestone (#7647)
chore: run prcop workflow on milestone
2024-03-27 09:09:57 -07:00
Jesse Mazzella
5b4ee1949f fix(#7623): Resize ConductorAxis properly (#7624)
* fix: resize conductor properly

* refactor: more computed properties, unregister listener

* fix: beforeUnmounted hook

* test(visual): add time conductor visual test for fixed mode

* fix: initialize to `null`

* feat: extend the base `screenshot` function to mask elements which will always create variance in an Open MCT screenshot

* docs: add types for fixtures

* fix: remove unneeded await

* chore: add sinon timers types package back

* docs: remove unused docs

* doc: remove unused docs

* test: add visual realtime url, update imports

* feat: provide wrapped page.screenshot fixture that applies defaults

* test: add basic timeConductor snapshot tests

* chore: update eslint config

* lint: remove unused disable directives

* test: remove redundant navigation

* fix: remove listeners

* fix: maybe stabilize unit tests

* docs: remove

* fix: provide sourcemaps in unit tests

* test: add regression snapshot test for time conductor axis

* lint: remove unused imports

* feat(e2e): add fixture to manually tick the clock and use it

* test: reactivate test now that we don't use deploysentinel :(

* test: update snapshots

* test: add test for clockOptions and tick fixtures

* test: add afterEach stub and fixme

* test: try and stabilize fault management flake

* lint: defy the word gods

* chore: ignore `*-darwin.png` screenshots

* chore: remove darwin screenshot binaries

* docs: markdownlint

* docs: remove MacOS specific instructions from snapshot testing

* fix: remove a11y
2024-03-26 23:52:33 +00:00
dependabot[bot]
7e926ccbb7 chore(deps-dev): bump webpack-dev-middleware from 7.0.0 to 7.1.1 (#7634)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 7.0.0 to 7.1.1.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v7.0.0...v7.1.1)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-26 15:23:17 -07:00
Jesse Mazzella
6e264517f8 docs: Mission Status and more (#7521)
* chore: update tsconfig to target modern ES

* docs: update UserProvider

* docs: update UserAPI, make openmct private

* docs: update StatusAPI

* refactor: convert ViewRegistry to ES6 class

* docs: finish type imports for openmct api

* docs: minor doc improvements

* docs: add UserIndicator readme

* docs: add User API section to API docs

* docs: document Mission Status

* docs(JSDoc): primitive types should be lowercase, otherwise TitleCase
2024-03-26 19:11:00 +00:00
Scott Bell
986da5782b Disable reload in preview (#7639)
disable reload in preview

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2024-03-26 16:58:47 +00:00
Scott Bell
539138437b For an existing View in a Preview, ensure we pull the same ActionCollection (#7632)
* ensure action collection returned is the cached one from the same view

* add test

* use async await
2024-03-26 14:11:23 +00:00
Jesse Mazzella
493b31d0b9 fix(#7633): add missing await (#7643)
* fix(#7633): add missing `await`

* test: add LAD table e2e suite + test
2024-03-26 10:14:25 +00:00
222 changed files with 3715 additions and 1463 deletions

View File

@@ -1,13 +1,21 @@
const LEGACY_FILES = ['example/**'];
module.exports = {
/** @type {import('eslint').Linter.Config} */
const config = {
env: {
browser: true,
es6: true,
es2024: true,
jasmine: true,
amd: true
node: true,
worker: true,
serviceworker: true
},
globals: {
_: 'readonly'
_: 'readonly',
__webpack_public_path__: 'writeable',
__OPENMCT_VERSION__: 'readonly',
__OPENMCT_BUILD_DATE__: 'readonly',
__OPENMCT_REVISION__: 'readonly',
__OPENMCT_BUILD_BRANCH__: 'readonly'
},
plugins: ['prettier', 'unicorn', 'simple-import-sort'],
extends: [
@@ -23,10 +31,11 @@ module.exports = {
parser: '@babel/eslint-parser',
requireConfigFile: false,
allowImportExportEverywhere: true,
ecmaVersion: 2015,
ecmaVersion: 'latest',
ecmaFeatures: {
impliedStrict: true
}
},
sourceType: 'module'
},
rules: {
'simple-import-sort/imports': 'warn',
@@ -152,7 +161,7 @@ module.exports = {
cases: {
pascalCase: true
},
ignore: ['^.*\\.js$']
ignore: ['^.*\\.(js|cjs|mjs)$']
}
],
'vue/first-attribute-linebreak': 'error',
@@ -179,3 +188,5 @@ module.exports = {
}
]
};
module.exports = config;

View File

@@ -5,6 +5,8 @@ on:
types:
- labeled
- unlabeled
- milestoned
- demilestoned
- opened
- reopened
- synchronize

3
.gitignore vendored
View File

@@ -47,3 +47,6 @@ index.html.bak
.nyc_output
coverage
codecov
# Don't commit MacOS screenshots
*-darwin.png

View File

@@ -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

View File

@@ -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
@@ -66,7 +69,6 @@ const config = {
'@': path.join(projectRootDir, 'src'),
legacyRegistry: path.join(projectRootDir, 'src/legacyRegistry'),
csv: 'comma-separated-values',
EventEmitter: 'eventemitter3',
bourbon: 'bourbon.scss',
'plotly-basic': 'plotly.js-basic-dist-min',
'plotly-gl2d': 'plotly.js-gl2d-dist-min',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

10
API.md
View File

@@ -1305,6 +1305,16 @@ View provider Example:
}
```
## User API
Open MCT provides a User API which can be used to define providers for user information. The API
can be used to manage user information and roles.
### Example
Open MCT provides an example [user](example/exampleUser/exampleUserCreator.js) and [user provider](example/exampleUser/ExampleUserProvider.js) which
can be used as a starting point for creating a custom user provider.
## Visibility-Based Rendering in View Providers
To enhance performance and resource efficiency in OpenMCT, a visibility-based rendering feature has been added. This feature is designed to defer the execution of rendering logic for views that are not currently visible. It ensures that views are only updated when they are in the viewport, similar to how modern browsers handle rendering of inactive tabs but optimized for the OpenMCT tabbed display. It also works when views are scrolled outside the viewport (e.g., in a Display Layout).

112
README.md
View File

@@ -5,12 +5,10 @@ Open MCT (Open Mission Control Technologies) is a next-generation mission contro
> [!NOTE]
> Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
Once you've created something amazing with Open MCT, showcase your work in our GitHub Discussions [Show and Tell](https://github.com/nasa/openmct/discussions/categories/show-and-tell) section. We love seeing unique and wonderful implementations of Open MCT!
![Screen Shot 2022-11-23 at 9 51 36 AM](https://user-images.githubusercontent.com/4215777/203617422-4d912bfc-766f-4074-8324-409d9bbe7c05.png)
## Building and Running Open MCT Locally
Building and running Open MCT in your local dev environment is very easy. Be sure you have [Git](https://git-scm.com/downloads) and [Node.js](https://nodejs.org/) installed, then follow the directions below. Need additional information? Check out the [Getting Started](https://nasa.github.io/openmct/getting-started/) page on our website.
@@ -18,19 +16,19 @@ Building and running Open MCT in your local dev environment is very easy. Be sur
1. Clone the source code:
```
```sh
git clone https://github.com/nasa/openmct.git
```
2. (Optional) Install the correct node version using [nvm](https://github.com/nvm-sh/nvm):
```
```sh
nvm install
```
3. Install development dependencies (Note: Check the `package.json` engine for our tested and supported node versions):
3. Install development dependencies (Note: Check the `package.json` engine for our tested and supported node versions):
```
```sh
npm install
```
@@ -57,9 +55,9 @@ our documentation.
> [!NOTE]
> We want Open MCT to be as easy to use, install, run, and develop for as
> possible, and your feedback will help us get there!
> Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose),
> [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions),
> possible, and your feedback will help us get there!
> Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues/new/choose),
> [Starting a GitHub Discussion](https://github.com/nasa/openmct/discussions),
> or by emailing us at [arc-dl-openmct@mail.nasa.gov](mailto:arc-dl-openmct@mail.nasa.gov).
## Developing Applications With Open MCT
@@ -68,28 +66,29 @@ For more on developing with Open MCT, see our documentation for a guide on [Deve
## Compatibility
This is a fast moving project and we do our best to test and support the widest possible range of browsers, operating systems, and nodejs APIs. We have a published list of support available in our package.json's `browserslist` key.
This is a fast moving project and we do our best to test and support the widest possible range of browsers, operating systems, and NodeJS APIs. We have a published list of support available in our package.json's `browserslist` key.
The project uses `nvm` to ensure the node and npm version used, is coherent in all projects. Install nvm (non-windows), [here](https://github.com/nvm-sh/nvm) or the windows equivalent [here](https://github.com/coreybutler/nvm-windows)
The project utilizes `nvm` to maintain consistent node and npm versions across all projects. For UNIX, MacOS, Windows WSL, and other POSIX-compliant shell environments, click [here](https://github.com/nvm-sh/nvm). For Windows, check out [nvm-windows](https://github.com/coreybutler/nvm-windows).
If you encounter an issue with a particular browser, OS, or nodejs API, please file a [GitHub issue](https://github.com/nasa/openmct/issues/new/choose)
If you encounter an issue with a particular browser, OS, or NodeJS API, please [file an issue](https://github.com/nasa/openmct/issues/new/choose).
## Plugins
Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group
Open MCT can be extended via plugins that make calls to the Open MCT API. A plugin is a group
of software components (including source code and resources such as images and HTML templates)
that is intended to be added or removed as a single unit.
As well as providing an extension mechanism, most of the core Open MCT codebase is also
As well as providing an extension mechanism, most of the core Open MCT codebase is also
written as plugins.
For information on writing plugins, please see [our API documentation](./API.md#plugins).
## Tests
Our automated test coverage comes in the form of unit, e2e, visual, performance, and security tests.
Our automated test coverage comes in the form of unit, e2e, visual, performance, and security tests.
### Unit Tests
Unit Tests are written for [Jasmine](https://jasmine.github.io/api/edge/global)
and run by [Karma](http://karma-runner.github.io). To run:
@@ -101,24 +100,34 @@ in the `src` hierarchy. Full configuration details are found in
alongside the units that they test; for example, `src/foo/Bar.js` would be
tested by `src/foo/BarSpec.js`.
### e2e, Visual, and Performance tests
The e2e, Visual, and Performance tests are written for playwright and run by playwright's new test runner [@playwright/test](https://playwright.dev/).
### e2e, Visual, and Performance Testing
To run the e2e tests which are part of every commit:
Our e2e (end-to-end), Visual, and Performance tests leverage the Playwright framework and are executed using Playwright's test runner, [@playwright/test](https://playwright.dev/).
`npm run test:e2e:stable`
#### How to Run Tests
To run the visual test suite:
- **e2e Tests**: These tests are run on every commit. To run the tests locally, use:
`npm run test:e2e:visual`
```sh
npm run test:e2e:stable
```
To run the performance tests:
- **Visual Tests**: For running the visual test suite, use:
`npm run test:perf`
```sh
npm run test:e2e:visual
```
The test suite is configured to all tests located in `e2e/tests/` ending in `*.e2e.spec.js`. For more about the e2e test suite, please see the [README](./e2e/README.md)
- **Performance Tests**: To initiate the performance tests, enter:
```sh
npm run test:perf
```
All tests are located within the `e2e/tests/` directory and are identified by the `*.e2e.spec.js` filename pattern. For more information about the e2e test suite, refer to the [README](./e2e/README.md).
### Security Tests
Each commit is analyzed for known security vulnerabilities using [CodeQL](https://codeql.github.com/docs/codeql-language-guides/codeql-library-for-javascript/). The list of CWE coverage items is available in the [CodeQL docs](https://codeql.github.com/codeql-query-help/javascript-cwe/). The CodeQL workflow is specified in the [CodeQL analysis file](./.github/workflows/codeql-analysis.yml) and the custom [CodeQL config](./.github/codeql/codeql-config.yml).
### Test Reporting and Code Coverage
@@ -129,60 +138,41 @@ Our code coverage is generated during the runtime of our unit, e2e, and visual t
For more on the specifics of our code coverage setup, [see](TESTING.md#code-coverage)
# Glossary
## Glossary
Certain terms are used throughout Open MCT with consistent meanings
or conventions. Any deviations from the below are issues and should be
addressed (either by updating this glossary or changing code to reflect
correct usage.) Other developer documentation, particularly in-line
documentation, may presume an understanding of these terms.
* _plugin_: A plugin is a removable, reusable grouping of software elements.
The application is composed of plugins.
* _composition_: In the context of a domain object, this refers to the set of
other domain objects that compose or are contained by that object. A domain
object's composition is the set of domain objects that should appear
immediately beneath it in a tree hierarchy. A domain object's composition is
described in its model as an array of id's; its composition capability
provides a means to retrieve the actual domain object instances associated
with these identifiers asynchronously.
* _description_: When used as an object property, this refers to the human-readable
description of a thing; usually a single sentence or short paragraph.
(Most often used in the context of extensions, domain
object models, or other similar application-specific objects.)
* _domain object_: A meaningful object to the user; a distinct thing in
the work support by Open MCT. Anything that appears in the left-hand
tree is a domain object.
* _identifier_: A tuple consisting of a namespace and a key, which together uniquely
identifies a domain object.
* _model_: The persistent state associated with a domain object. A domain
object's model is a JavaScript object which can be converted to JSON
without losing information (that is, it contains no methods.)
* _name_: When used as an object property, this refers to the human-readable
name for a thing. (Most often used in the context of extensions, domain
object models, or other similar application-specific objects.)
* _navigation_: Refers to the current state of the application with respect
to the user's expressed interest in a specific domain object; e.g. when
a user clicks on a domain object in the tree, they are _navigating_ to
it, and it is thereafter considered the _navigated_ object (until the
user makes another such choice.)
* _namespace_: A name used to identify a persistence store. A running open MCT
application could potentially use multiple persistence stores, with the
| Term | Definition |
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| _plugin_ | A removable, reusable grouping of software elements. The application is composed of plugins. |
| _composition_ | In the context of a domain object, this term refers to the set of other domain objects that compose or are contained by that object. A domain object's composition is the set of domain objects that should appear immediately beneath it in a tree hierarchy. It is described in its model as an array of ids, providing a means to asynchronously retrieve the actual domain object instances associated with these identifiers. |
| _description_ | When used as an object property, this term refers to the human-readable description of a thing, usually a single sentence or short paragraph. It is most often used in the context of extensions, domain object models, or other similar application-specific objects. |
| _domain object_ | A meaningful object to the user and a distinct thing in the work supported by Open MCT. Anything that appears in the left-hand tree is a domain object. |
| _identifier_ | A tuple consisting of a namespace and a key, which together uniquely identifies a domain object. |
| _model_ | The persistent state associated with a domain object. A domain object's model is a JavaScript object that can be converted to JSON without losing information, meaning it contains no methods. |
| _name_ | When used as an object property, this term refers to the human-readable name for a thing. It is most often used in the context of extensions, domain object models, or other similar application-specific objects. |
| _navigation_ | This term refers to the current state of the application with respect to the user's expressed interest in a specific domain object. For example, when a user clicks on a domain object in the tree, they are navigating to it, and it is thereafter considered the navigated object until the user makes another such choice. |
| _namespace_ | A name used to identify a persistence store. A running Open MCT application could potentially use multiple persistence stores. |
## Open MCT v2.0.0
Support for our legacy bundle-based API, and the libraries that it was built on (like Angular 1.x), have now been removed entirely from this repository.
For now if you have an Open MCT application that makes use of the legacy API, [a plugin](https://github.com/nasa/openmct-legacy-plugin) is provided that bootstraps the legacy bundling mechanism and API. This plugin will not be maintained over the long term however, and the legacy support plugin will not be tested for compatibility with future versions of Open MCT. It is provided for convenience only.
### How do I know if I am using legacy API?
You might still be using legacy API if your source code
* Contains files named bundle.js, or bundle.json,
* Makes calls to `openmct.$injector()`, or `openmct.$angular`,
* Makes calls to `openmct.legacyRegistry`, `openmct.legacyExtension`, or `openmct.legacyBundle`.
- Contains files named bundle.js, or bundle.json,
- Makes calls to `openmct.$injector()`, or `openmct.$angular`,
- Makes calls to `openmct.legacyRegistry`, `openmct.legacyExtension`, or `openmct.legacyBundle`.
### What should I do if I am using legacy API?
Please refer to [the modern Open MCT API](https://nasa.github.io/openmct/documentation/). Post any questions to the [Discussions section](https://github.com/nasa/openmct/discussions) of the Open MCT GitHub repository.
## Related Repos

View File

@@ -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
View File

@@ -0,0 +1,7 @@
*
!appActions.js
!baseFixtures.js
!pluginFixtures.js
!avpFixtures.js
!index.js
!*.md

View File

@@ -76,28 +76,30 @@ To read about how to write a good visual test, please see [How to write a great
`npm run test:e2e:visual` commands will run all of the visual tests against a local instance of Open MCT. If no `PERCY_TOKEN` API key is found in the terminal or command line environment variables, no visual comparisons will be made.
- `npm run test:e2e:visual:ci` will run against every commit and PR.
- `npm run test:e2e:visual:full` will run every night with additional comparisons made for Larger Displays and with the `snow` theme.
- `npm run test:e2e:visual:ci` will run against every commit and PR.
- `npm run test:e2e:visual:full` will run every night with additional comparisons made for Larger Displays and with the `snow` theme.
#### Percy.io
To make this possible, we're leveraging a 3rd party service, [Percy](https://percy.io/). This service maintains a copy of all changes, users, scm-metadata, and baselines to verify that the application looks and feels the same _unless approved by a Open MCT developer_. To request a Percy API token, please reach out to the Open MCT Dev team on GitHub. For more information, please see the official [Percy documentation](https://docs.percy.io/docs/visual-testing-basics).
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots.
At present, we are using percy with two configuration files: `./e2e/.percy.nightly.yml` and `./e2e/.percy.ci.yml`. This is mainly to reduce the number of snapshots.
### Advanced: Snapshot Testing (Not Recommended)
While snapshot testing offers a precise way to detect changes in your application without relying on third-party services like Percy.io, we've found that it doesn't offer any advantages over visual testing in our use-cases. Therefore, snapshot testing is **not recommended** for further implementation.
#### CI vs Manual Checks
Snapshot tests can be reliably executed in Continuous Integration (CI) environments but lack the manual oversight provided by visual testing platforms like Percy.io. This means they may miss issues that a human reviewer could catch during manual checks.
#### Example
A single visual test assertion in Percy.io can be executed across 10 different browser and resolution combinations without additional setup, providing comprehensive testing with minimal configuration. In contrast, a snapshot test is restricted to a single OS and browser resolution, requiring more effort to achieve the same level of coverage.
#### Further Reading
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
For those interested in the mechanics of snapshot testing with Playwright, you can refer to the [Playwright Snapshots Documentation](https://playwright.dev/docs/test-snapshots). However, keep in mind that we do not recommend using this approach.
#### Open MCT's implementation
@@ -118,14 +120,6 @@ When the `@snapshot` tests fail, they will need to be evaluated to determine if
To compare a snapshot, run a test and open the html report with the 'Expected' vs 'Actual' screenshot. If the actual screenshot is preferred, then the source-controlled 'Expected' snapshots will need to be updated with the following scripts.
MacOS
```
npm run test:e2e:updatesnapshots
```
Linux/CI
```sh
// Replace {X.X.X} with the current Playwright version
// from our package.json or circleCI configuration file
@@ -173,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.
@@ -335,9 +329,11 @@ We have a Mission-need to support iPad and mobile devices. To run our test suite
In general, our test suite is not designed to run against mobile devices as the mobile experience is a focused version of the application. Core functionality is missing (chiefly the 'Create' button). To bypass the object creation, we leverage the `storageState` properties for starting the mobile tests with localstorage.
For now, the mobile tests will exist in the /tests/mobile/ suites and be executed with the
```sh
npm run test:e2e:mobile
```
command.
#### **Skipping or executing tests based on browser, os, and/os browser version:**
@@ -377,6 +373,7 @@ In general, strive to test only through the UI as a user would. As stated in the
By adhering to this principle, we can create tests that are both robust and reflective of actual user experiences.
#### How to make tests robust to function in other contexts (VISTA, COUCHDB, YAMCS, VIPER, etc.)
1. Leverage the use of `appActions.js` methods such as `createDomainObjectWithDefaults()`. This ensures that your tests will create unique instances of objects for your test to interact with.
1. Do not assert on the order or structure of objects available unless you created them yourself. These tests may be used against a persistent datastore like couchdb with many objects in the tree.
1. Do not search for your created objects. Open MCT does not performance uniqueness checks so it's possible that your tests will break when run twice.
@@ -384,6 +381,7 @@ By adhering to this principle, we can create tests that are both robust and refl
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });` instead of `{ waitUntil: 'networkidle' }`. Tests run against deployments with websockets often have issues with the networkidle detection.
#### How to make tests faster and more resilient
1. Avoid app interaction when possible. The best way of doing this is to navigate directly by URL:
```js
@@ -396,10 +394,11 @@ By adhering to this principle, we can create tests that are both robust and refl
1. Leverage `await page.goto('./', { waitUntil: 'domcontentloaded' });`
- Initial navigation should _almost_ always use the `{ waitUntil: 'domcontentloaded' }` option.
1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions.
1. Avoid repeated setup to test a single assertion. Write longer tests with multiple soft assertions.
This ensures that your changes will be picked up with large refactors.
##### Utilizing LocalStorage
1. In order to save test runtime in the case of tests that require a decent amount of initial setup (such as in the case of testing complex displays), you may use [Playwright's `storageState` feature](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state) to generate and load localStorage states.
1. To generate a localStorage state to be used in a test:
- Add an e2e test to our generateLocalStorageData suite which sets the initial state (creating/configuring objects, etc.), saving it in the `test-data` folder:
@@ -420,7 +419,6 @@ By adhering to this principle, we can create tests that are both robust and refl
});
```
### How to write a great test
- Avoid using css locators to find elements to the page. Use modern web accessible locators like `getByRole`
@@ -436,7 +434,7 @@ By adhering to this principle, we can create tests that are both robust and refl
await notesInput.fill(testNotes);
```
#### How to Write a Great Visual Test
#### How to Write a Great Visual Test
1. **Look for the Unknown Unknowns**: Avoid asserting on specific differences in the visual diff. Visual tests are most effective for identifying unknown unknowns.
@@ -445,23 +443,27 @@ By adhering to this principle, we can create tests that are both robust and refl
3. **Expect the Unexpected**: Use functional expect statements only to verify assumptions about the state between steps. A great visual test doesn't fail during the test itself, but rather when changes are reviewed in Percy.io.
4. **Control Variability**: Account for variations inherent in working with time-based telemetry and clocks.
- Utilize `percyCSS` to ignore time-based elements. For more details, consult our [percyCSS file](./.percy.ci.yml).
- Use Open MCT's fixed-time mode unless explicitly testing realtime clock
- Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names.
- Avoid creating objects with a time component like timers and clocks.
- Utilize `percyCSS` to ignore time-based elements. For more details, consult our [percyCSS file](./.percy.ci.yml).
- Use Open MCT's fixed-time mode unless explicitly testing realtime clock
- Employ the `createExampleTelemetryObject` appAction to source telemetry and specify a `name` to avoid autogenerated names.
- Avoid creating objects with a time component like timers and clocks.
5. **Hide the Tree and Inspector**: Generally, your test will not require comparisons involving the tree and inspector. These aspects are covered in component-specific tests (explained below). To exclude them from the comparison by default, navigate to the root of the main view with the tree and inspector hidden:
- `await page.goto('./#/browse/mine?hideTree=true&hideInspector=true')`
6. **Component-Specific Tests**: If you wish to focus on a particular component, use the `/visual-a11y/component/` folder and limit the scope of the comparison to that component. For instance:
```js
await percySnapshot(page, `Tree Pane w/ single level expanded (theme: ${theme})`, {
scope: treePane
});
```
- Note: The `scope` variable can be any valid CSS selector.
7. **Write many `percySnapshot` commands in a single test**: In line with our approach to longer functional tests, we recommend that many test percySnapshots are taken in a single test. For instance:
```js
//<Some interesting state>
await percySnapshot(page, `Before object expanded (theme: ${theme})`);
@@ -511,6 +513,7 @@ test.describe('foo test suite', () => {
});
});
```
More info and options for `overrideClock` can be found in [baseFixtures.js](baseFixtures.js)
- Working with multiple pages
@@ -539,7 +542,6 @@ const key = getFirstKeyFromOpenMctJson(jsonData);
expect(jsonData.openmct[key]).toHaveProperty('name', 'e2e folder');
```
### Reporting
Test Reporting is done through official Playwright reporters and the CI Systems which execute them.
@@ -615,6 +617,7 @@ A single e2e test in Open MCT is extended to run:
### Writing Tests
Playwright provides 3 supported methods of debugging and authoring tests:
- A 'watch mode' for running tests locally and debugging on the fly
- A 'debug mode' for debugging tests and writing assertions against tests
- A 'VSCode plugin' for debugging tests within the VSCode IDE.

View File

@@ -35,7 +35,7 @@
* @property {string} type the type of domain object to create (e.g.: "Sine Wave Generator").
* @property {string} [name] the desired name of the created domain object.
* @property {string | import('../src/api/objects/ObjectAPI').Identifier} [parent] the Identifier or uuid of the parent object.
* @property {Object<string, string>} [customParameters] any additional parameters to be passed to the domain object's form. E.g. '[aria-label="Data Rate (hz)"]': {'0.1'}
* @property {Record<string, string>} [customParameters] any additional parameters to be passed to the domain object's form. E.g. '[aria-label="Data Rate (hz)"]': {'0.1'}
*/
/**

View File

@@ -36,27 +36,67 @@
import AxeBuilder from '@axe-core/playwright';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { expect, test } from './pluginFixtures.js';
// Constants for repeated values
const TEST_RESULTS_DIR = './test-results';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const TEST_RESULTS_DIR = path.join(__dirname, './test-results');
const extendedTest = test.extend({
/**
* Overrides the default screenshot function to apply default options that should apply to all
* screenshots taken in the AVP tests.
*
* @param {import('@playwright/test').PlaywrightTestArgs} args - The Playwright test arguments.
* @param {Function} use - The function to use the page object.
* Defaults:
* - Disables animations
* - Masks the clock indicator
* - Masks the time conductor last update time in realtime mode
* - Masks the time conductor start bounds in fixed mode
* - Masks the time conductor end bounds in fixed mode
*/
page: async ({ page }, use) => {
const playwrightScreenshot = page.screenshot;
/**
* Override the screenshot function to always mask a given set of locators which will always
* show variance across screenshots. Defaults may be overridden by passing in options to the
* screenshot function.
* @param {import('@playwright/test').PageScreenshotOptions} options - The options for the screenshot.
* @returns {Promise<Buffer>} Returns the screenshot as a buffer.
*/
page.screenshot = async function (options = {}) {
const mask = [
this.getByLabel('Clock Indicator'), // Mask the clock indicator
this.getByLabel('Last update'), // Mask the time conductor last update time in realtime mode
this.getByLabel('Start bounds'), // Mask the time conductor start bounds in fixed mode
this.getByLabel('End bounds') // Mask the time conductor end bounds in fixed mode
];
const result = await playwrightScreenshot.call(this, {
animations: 'disabled',
mask,
...options // Pass through or override any options
});
return result;
};
await use(page);
}
});
/**
* Scans for accessibility violations on a page and writes a report to disk if violations are found.
* Automatically asserts that no violations should be present.
*
* @typedef {object} GenerateReportOptions
* @property {string} [reportName] - The name for the report file.
*
* @param {import('playwright').Page} page - The page object from Playwright.
* @param {string} testCaseName - The name of the test case.
* @param {GenerateReportOptions} [options={}] - The options for the report generation.
*
* @returns {Promise<object|null>} Returns the accessibility scan results if violations are found,
* otherwise returns null.
* @param {{ reportName?: string }} [options={}] - The options for the report generation.
* @returns {Promise<Object|null>} Returns the accessibility scan results if violations are found, otherwise returns null.
*/
/* eslint-disable no-undef */
export async function scanForA11yViolations(page, testCaseName, options = {}) {
const builder = new AxeBuilder({ page });
builder.withTags(['wcag2aa']);
@@ -93,4 +133,4 @@ export async function scanForA11yViolations(page, testCaseName, options = {}) {
}
}
export { expect, test };
export { expect, extendedTest as test };

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-undef */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@@ -40,7 +39,7 @@ import { v4 as uuid } from 'uuid';
* @see {@link https://github.com/microsoft/playwright/discussions/11690 Github Discussion}
* @private
* @param {import('@playwright/test').ConsoleMessage} msg
* @returns {String} formatted string with message type, text, url, and line and column numbers
* @returns {string} formatted string with message type, text, url, and line and column numbers
*/
function _consoleMessageToString(msg) {
const { url, lineNumber, columnNumber } = msg.location();
@@ -61,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.
@@ -111,21 +112,55 @@ const extendedTest = test.extend({
scope: 'test'
}
],
/**
* Exposes a function to manually tick the clock. This is useful when overriding the clock to not
* tick (`shouldAdvanceTime: false`) for visual tests, as events such as re-renders and router params
* updates are clock-driven and must be manually ticked.
*
* Usage:
* ```js
* test.describe('Manual Clock Tick', () => {
* test.use({
* clockOptions: {
* now: MISSION_TIME, // Set to the desired time
* shouldAdvanceTime: false // Clock overridden to no longer tick
* }
* });
* test('Visual - Manual Clock Tick', async ({ page, tick }) => {
* // Tick the clock 2 seconds in the future
* await tick(2000);
* });
* });
* ```
*
* @param {Object} param0
* @param {import('@playwright/test').Page} param0.page
* @param {import('@playwright/test').Use} param0.use
*/
tick: async ({ page }, use) => {
// eslint-disable-next-line func-style
const tick = async (milliseconds) => {
await page.evaluate((_milliseconds) => {
window.__clock.tick(_milliseconds);
}, milliseconds);
};
await use(tick);
},
/**
* 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
);
}
@@ -133,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__));
});
}
},
/**
@@ -154,17 +189,13 @@ const extendedTest = test.extend({
// function in the generatorWorker context. This is necessary
// to ensure that example telemetry data is generated for the new clock time.
if (clockOptions?.now !== undefined) {
page.on(
'worker',
(worker) => {
if (worker.url().includes('generatorWorker')) {
worker.evaluate((time) => {
self.Date.now = () => time;
});
}
},
clockOptions.now
);
page.on('worker', (worker) => {
if (worker.url().includes('generatorWorker')) {
worker.evaluate((time) => {
self.Date.now = () => time;
}, clockOptions.now);
}
});
}
// Capture any console errors during test execution

View File

@@ -1,4 +1,3 @@
/* eslint-disable prettier/prettier */
/**
* Constants which may be used across all e2e tests.
*/
@@ -8,12 +7,30 @@
* - Used for overriding the browser clock in tests.
*/
export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:00 PM GMT-08:00 (Thanksgiving Dinner Time)
// Subtracting 30 minutes from MISSION_TIME
export const MISSION_TIME_FIXED_START = 1732413600000 - 1800000; // 1732411800000
// Adding 1 minute to MISSION_TIME
export const MISSION_TIME_FIXED_END = 1732413600000 + 60000; // 1732413660000
/**
* URL Constants
* - This is the URL that the browser will be directed to when running visual tests. This URL
* - hides the tree and inspector to prevent visual noise
* - sets the time bounds to a fixed range
* These constants are used for initial navigation in visual tests, in either fixed or realtime mode.
* They navigate to the 'My Items' folder at MISSION_TIME.
* They set the following url parameters:
* - tc.mode - The time conductor mode ('fixed' or 'local')
* - tc.startBound - The time conductor start bound (when in fixed mode)
* - tc.endBound - The time conductor end bound (when in fixed mode)
* - tc.startDelta - The time conductor start delta (when in realtime mode)
* - tc.endDelta - The time conductor end delta (when in realtime mode)
* - tc.timeSystem - The time conductor time system ('utc')
* - view - The view to display ('grid')
* - hideInspector - Whether to hide the inspector (true)
* - hideTree - Whether to hide the tree (true)
* @typedef {string} VisualUrl
*/
export const VISUAL_URL =
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
/** @type {VisualUrl} */
export const VISUAL_FIXED_URL = `./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`;
/** @type {VisualUrl} */
export const VISUAL_REALTIME_URL =
'./#/browse/mine?tc.mode=local&tc.timeSystem=utc&view=grid&tc.startDelta=1800000&tc.endDelta=30000&hideTree=true&hideInspector=true';

View File

@@ -68,7 +68,6 @@ async function commitEntry(page) {
* @param {import('@playwright/test').Page} page
*/
async function startAndAddRestrictedNotebookObject(page) {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(new URL('./addInitRestrictedNotebook.js', import.meta.url))
});

View File

@@ -29,7 +29,7 @@ import { expect } from '../pluginFixtures.js';
* for each activity in the plan data per group, using the earliest activity's
* start time as the start bound and the current activity's end time as the end bound.
* @param {import('@playwright/test').Page} page the page
* @param {object} plan The raw plan json to assert against
* @param {Object} plan The raw plan json to assert against
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
*/
export async function assertPlanActivities(page, plan, objectUrl) {
@@ -86,7 +86,7 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
* Asserts that the swim lanes / groups in the plan view matches the order of
* groups in the plan data.
* @param {import('@playwright/test').Page} page the page
* @param {object} plan The raw plan json to assert against
* @param {Object} plan The raw plan json to assert against
* @param {string} objectUrl The URL of the object to assert against (plan or gantt chart)
*/
export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
@@ -110,7 +110,7 @@ export async function assertPlanOrderedSwimLanes(page, plan, objectUrl) {
* Navigate to the plan view, switch to fixed time mode,
* and set the bounds to span all activities.
* @param {import('@playwright/test').Page} page
* @param {object} planJson
* @param {Object} planJson
* @param {string} planObjectUrl
*/
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
@@ -125,7 +125,7 @@ export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl
}
/**
* @param {object} planJson
* @param {Object} planJson
* @returns {number}
*/
export function getEarliestStartTime(planJson) {
@@ -135,7 +135,7 @@ export function getEarliestStartTime(planJson) {
/**
*
* @param {object} planJson
* @param {Object} planJson
* @returns {number}
*/
export function getLatestEndTime(planJson) {

View File

@@ -27,8 +27,8 @@ import { expect } from '../pluginFixtures.js';
* Given a canvas and a set of points, tags the points on the canvas.
* @param {import('@playwright/test').Page} page
* @param {HTMLCanvasElement} canvas a telemetry item with a plot
* @param {Number} xEnd a telemetry item with a plot
* @param {Number} yEnd a telemetry item with a plot
* @param {number} xEnd a telemetry item with a plot
* @param {number} yEnd a telemetry item with a plot
* @returns {Promise}
*/
export async function createTags({ page, canvas, xEnd = 700, yEnd = 520 }) {

8
e2e/index.js Normal file
View 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

File diff suppressed because it is too large Load Diff

27
e2e/package.json Normal file
View 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"
}

View File

@@ -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: [
{

View File

@@ -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

View File

@@ -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: [
{

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-undef */
// 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
@@ -11,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

View File

@@ -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.

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-undef */
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
@@ -123,7 +122,6 @@ const extendedTest = test.extend({
theme: [theme, { option: true }],
// eslint-disable-next-line no-shadow
page: async ({ page, theme }, use, testInfo) => {
// eslint-disable-next-line playwright/no-conditional-in-test
if (theme === 'snow') {
//inject snow theme
await page.addInitScript({

View File

@@ -26,11 +26,12 @@ relates to how we've extended it (i.e. ./e2e/baseFixtures.js) and assumptions ma
(`npm start` and ./e2e/webpack-dev-middleware.js)
*/
import { test } from '../../baseFixtures.js';
import { expect, test } from '../../baseFixtures.js';
import { MISSION_TIME } from '../../constants.js';
test.describe('baseFixtures tests', () => {
//Skip this test for now https://github.com/nasa/openmct/issues/6785
test.fixme('Verify that tests fail if console.error is thrown', async ({ page }) => {
test('Verify that tests fail if console.error is thrown', async ({ page }) => {
test.fail();
//Go to baseURL
await page.goto('./', { waitUntil: 'domcontentloaded' });
@@ -52,3 +53,27 @@ test.describe('baseFixtures tests', () => {
]);
});
});
test.describe('baseFixtures tests @clock', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: false
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
test('Can use clockOptions and tick fixtures to control the clock', async ({ page, tick }) => {
let time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME);
await tick(1000);
time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME + 1000 * 1);
await tick(1000);
time = await page.evaluate(() => new Date().getTime());
expect(time).toBe(MISSION_TIME + 1000 * 2);
});
});

View File

@@ -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 ({

View File

@@ -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;
}

View File

@@ -24,7 +24,7 @@
This test suite is dedicated to tests which verify the basic operations surrounding imagery,
but only assume that example imagery is present.
*/
/* globals process */
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../../../appActions.js';
import { waitForAnimations } from '../../../../baseFixtures.js';
import { expect, test } from '../../../../pluginFixtures.js';
@@ -773,7 +773,7 @@ async function dragContrastSliderAndAssertFilterValues(page) {
* Gets the filter:brightness value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected brightness value
* @param {string} expected The expected brightness value
*/
async function assertBackgroundImageBrightness(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
@@ -938,7 +938,7 @@ async function buttonZoomOnImageAndAssert(page) {
* Gets the filter:contrast value of the current background-image and
* asserts against an expected value
* @param {import('@playwright/test').Page} page
* @param {String} expected The expected contrast value
* @param {string} expected The expected contrast value
*/
async function assertBackgroundImageContrast(page, expected) {
const backgroundImage = page.locator('.c-imagery__main-image__background-image');

View File

@@ -27,7 +27,6 @@ import { expect, test } from '../../../../pluginFixtures.js';
test.describe('Testing numeric data with inspector data visualization (i.e., data pivoting)', () => {
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(
new URL('../../../../helper/addInitDataVisualization.js', import.meta.url)

View File

@@ -0,0 +1,86 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2024, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createDomainObjectWithDefaults } from '../../../../appActions.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('LAD Table', () => {
let ladTable;
let swg;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table'
});
swg = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
parent: ladTable.uuid
});
await page.goto(ladTable.url);
});
test('Ensure we have numbers in cells', async ({ page }) => {
// Wait for the initial value to show after mount
await expect(page.getByLabel('lad value').first()).not.toContainText('---');
const valueFromFirstSineWave = await page.getByLabel('lad value').first().innerText();
const firstSineWaveNumber = parseFloat(valueFromFirstSineWave);
// ensure we have a float value in the cell and it's finite
expect(Number.isFinite(firstSineWaveNumber)).toBeTruthy();
const valueFromSecondSineWave = await page.getByLabel('lad value').last().innerText();
const secondSineWaveNumber = parseFloat(valueFromSecondSineWave);
// ensure we have a float value in the cell and it's finite
expect(Number.isFinite(secondSineWaveNumber)).toBeTruthy();
});
test(
'Can remove telemetry from composition',
{
annotation: {
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7633'
}
},
async ({ page }) => {
// Assert that the table is initially populated
await expect(page.getByLabel('lad row')).toHaveCount(1);
// Expand the tree so the SWG is visible
await page.getByLabel('Expand My Items').click();
await page.getByLabel('Expand LAD Table').click();
// Right-click the SWG treeitem context menu and click 'Remove' and confirm
await page.getByRole('treeitem', { name: swg.name }).click({ button: 'right' });
await page.getByRole('menuitem', { name: 'Remove' }).click();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// Assert that the SWG is no longer in the tree and the table is empty
await expect(page.getByRole('treeitem', { name: swg.name })).toBeHidden();
await expect(page.getByLabel('lad row')).toHaveCount(0);
}
);
});

View File

@@ -277,7 +277,6 @@ test.describe('Notebook entry tests', () => {
// Create Notebook with URL Whitelist
let notebookObject;
test.beforeEach(async ({ page }) => {
// eslint-disable-next-line no-undef
await page.addInitScript({
path: fileURLToPath(new URL('../../../../helper/addInitNotebookWithUrls.js', import.meta.url))
});

View File

@@ -122,4 +122,14 @@ test.describe('Reload action', () => {
expect(fullReloadAlphaTelemetryValue).not.toEqual(afterReloadAlphaTelemetryValue);
expect(fullReloadBetaTelemetryValue).not.toEqual(afterReloadBetaTelemetryValue);
});
test('is disabled in Previews', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/7638'
});
await page.getByLabel('Alpha Table Frame Controls').getByLabel('Large View').click();
await page.getByLabel('Modal Overlay').getByLabel('More actions').click();
await expect(page.getByLabel('Reload')).toBeHidden();
});
});

View File

@@ -69,5 +69,9 @@ test.describe('Preview mode', () => {
await page.getByLabel('Overlay').getByLabel('More actions').click();
await expect(page.getByLabel('Export Table Data')).toBeVisible();
await expect(page.getByLabel('Export Marked Rows')).toBeVisible();
await expect(page.getByLabel('Export Marked Rows')).toBeDisabled();
await page.getByLabel('Pause').click();
const tableWrapper = page.getByLabel('Preview Container').locator('div.c-table-wrapper');
await expect(tableWrapper).toHaveClass(/is-paused/);
});
});

View File

@@ -48,7 +48,7 @@ test.describe('Time conductor operations', () => {
await setTimeConductorBounds(page, startDate);
// Bring up the time conductor popup
const timeConductorMode = await page.locator('.c-compact-tc');
const timeConductorMode = page.locator('.c-compact-tc');
await timeConductorMode.click();
const startDateLocator = page.locator('input[type="text"]').first();
const endDateLocator = page.locator('input[type="text"]').nth(2);

View File

@@ -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) => {

View File

@@ -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) => {

View File

@@ -299,7 +299,6 @@ test.describe('Navigation memory leak is not detected in', () => {
// for detecting memory leaks.
await page.evaluate(() => {
window.gcPromise = new Promise((resolve) => {
// eslint-disable-next-line no-undef
window.fr = new FinalizationRegistry(resolve);
window.fr.register(
window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild,

View File

@@ -21,14 +21,13 @@
*****************************************************************************/
import { test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('a11y - Default', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('main view', async ({ page }, testInfo) => {
await page.goto('./');
//Skipping for https://github.com/nasa/openmct/issues/7421
//await scanForA11yViolations(page, testInfo.title);
});

View File

@@ -27,12 +27,12 @@ Tests the branding associated with the default deployment. At least the about mo
import percySnapshot from '@percy/playwright';
import { expect, scanForA11yViolations, test } from '../../../avpFixtures.js';
import { VISUAL_URL } from '../../../constants.js';
import { VISUAL_FIXED_URL } from '../../../constants.js';
test.describe('Visual - Branding @a11y', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Visual - About Modal', async ({ page, theme }) => {

View File

@@ -28,7 +28,7 @@ import percySnapshot from '@percy/playwright';
import { fileURLToPath } from 'url';
import { expect, test } from '../../../avpFixtures.js';
import { VISUAL_URL } from '../../../constants.js';
import { VISUAL_FIXED_URL } from '../../../constants.js';
//Declare the component scope of the visual test for Percy
const header = '.l-shell__head';
@@ -36,7 +36,7 @@ const header = '.l-shell__head';
test.describe('Visual - Header @a11y', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
// Wait for status bar to load
await expect(
page.getByRole('status', {

View File

@@ -23,17 +23,17 @@
import percySnapshot from '@percy/playwright';
import { test } from '../../../avpFixtures.js';
import { MISSION_TIME, VISUAL_URL } from '../../../constants.js';
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../../constants.js';
//Declare the scope of the visual test
const inspectorPane = '.l-shell__pane-inspector';
test.describe('Visual - Inspector @ally @clock', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
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

View File

@@ -0,0 +1,116 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*
Tests the visual appearance of the Time Conductor component
*/
import { expect, test } from '../../../avpFixtures.js';
import {
MISSION_TIME,
MISSION_TIME_FIXED_END,
MISSION_TIME_FIXED_START,
VISUAL_REALTIME_URL
} from '../../../constants.js';
test.describe('Visual - Time Conductor', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: false
}
});
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
});
// FIXME: checking for a11y violations times out. Might have something to do with the frozen clock.
// test.afterEach(async ({ page }, testInfo) => {
// await scanForA11yViolations(page, testInfo.title);
// });
test('Visual - Time Conductor (Fixed time) @clock @snapshot', async ({ page }) => {
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed
await page.goto(
`./#/browse/mine?tc.mode=fixed&tc.startBound=${MISSION_TIME_FIXED_START}&tc.endBound=${MISSION_TIME_FIXED_END}&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true`,
{
waitUntil: 'domcontentloaded'
}
);
// Take a snapshot for comparison
const snapshot = await page.screenshot({
mask: []
});
expect(snapshot).toMatchSnapshot('time-conductor-fixed-time.png');
});
test('Visual - Time Conductor (Realtime) @clock @snapshot', async ({ page }) => {
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect and browse panes collapsed
await page.goto(VISUAL_REALTIME_URL, {
waitUntil: 'domcontentloaded'
});
const mask = [];
// Take a snapshot for comparison
const snapshot = await page.screenshot({
mask
});
expect(snapshot).toMatchSnapshot('time-conductor-realtime.png');
});
test(
'Visual - Time Conductor Axis Resized @clock @snapshot',
{ annotation: [{ type: 'issue', description: 'https://github.com/nasa/openmct/issues/7623' }] },
async ({ page, tick }) => {
const VISUAL_REALTIME_WITH_PANES = VISUAL_REALTIME_URL.replace(
'hideTree=true',
'hideTree=false'
).replace('hideInspector=true', 'hideInspector=false');
// Navigate to a specific view that uses the Time Conductor in Fixed Time mode with inspect
await page.goto(VISUAL_REALTIME_WITH_PANES, {
waitUntil: 'domcontentloaded'
});
// Set the time conductor to fixed time mode
await page.getByLabel('Time Conductor Mode').click();
await page.getByLabel('Time Conductor Mode Menu').click();
await page.getByLabel('Fixed Timespan').click();
await page.getByLabel('Submit time bounds').click();
// Collapse the inspect and browse panes to trigger a resize of the conductor axis
await page.getByLabel('Collapse Inspect Pane').click();
await page.getByLabel('Collapse Browse Pane').click();
// manually tick the clock to trigger the resize / re-render
await tick(1000 * 2);
const mask = [];
// Take a snapshot for comparison
const snapshot = await page.screenshot({
mask
});
expect(snapshot).toMatchSnapshot('time-conductor-axis-resized.png');
}
);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -23,7 +23,7 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../../appActions.js';
import { VISUAL_URL } from '../../../constants.js';
import { VISUAL_FIXED_URL } from '../../../constants.js';
import { test } from '../../../pluginFixtures.js';
//Declare the scope of the visual test
@@ -32,7 +32,7 @@ const treePane = "[role=tree][aria-label='Main Tree']";
test.describe('Visual - Tree Pane', () => {
test('Tree pane in various states', async ({ page, theme, openmctConfig }) => {
const { myItemsFolderName } = openmctConfig;
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
//Open Tree
await page.getByRole('button', { name: 'Browse' }).click();

View File

@@ -27,15 +27,15 @@ clockOptions plugin fixture.
import percySnapshot from '@percy/playwright';
import { MISSION_TIME, VISUAL_URL } from '../../constants.js';
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - Controlled Clock @clock', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
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
@@ -43,7 +43,7 @@ test.describe('Visual - Controlled Clock @clock', () => {
});
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await page
.getByRole('gridcell', { hasText: 'Overlay Plot with 5s Delay Overlay Plot' })
.click();

View File

@@ -23,18 +23,18 @@
/*
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';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('Visual - Default @a11y', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Visual - Default Dashboard', async ({ page, theme }) => {

View File

@@ -23,12 +23,18 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { VISUAL_URL } from '../../constants.js';
import { MISSION_TIME, VISUAL_FIXED_URL } from '../../constants.js';
import { test } from '../../pluginFixtures.js';
test.describe('Visual - Display Layout', () => {
test.beforeEach(async ({ page, theme }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
test.describe('Visual - Display Layout @clock', () => {
test.use({
clockOptions: {
now: MISSION_TIME,
shouldAdvanceTime: true
}
});
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
const parentLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',
@@ -59,12 +65,15 @@ test.describe('Visual - Display Layout', () => {
await page.goto(parentLayout.url, { waitUntil: 'domcontentloaded' });
await page.getByRole('button', { name: 'Edit Object' }).click();
//Move the Child Right Layout to the Right. It should be on top of the Left Layout at this point.
// Select the child right layout
await page
.getByLabel('Child Right Layout Layout', { exact: true })
.getByLabel('Move Sub-object Frame')
.click();
await page.getByLabel('Move Sub-object Frame').nth(3).click(); //I'm not sure why this step is necessary
// FIXME: Click to select the parent object (layout)
await page.getByLabel('Move Sub-object Frame').nth(3).click();
// Move the second layout element to the right
await page.getByLabel('X:').click();
await page.getByLabel('X:').fill('35');
});

View File

@@ -84,6 +84,12 @@ test.describe('Fault Management Visual Tests', () => {
await shelveFault(page, 1);
await changeViewTo(page, 'shelved');
/* cspell:disable-next-line */
// Since fault management is heavily dependent on events (bleh), we need to wait for the correct
// element counts
await expect(page.getByLabel('Select fault:')).toHaveCount(1);
await expect(page.getByLabel('Disposition Actions')).toHaveCount(1);
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
await openFaultRowMenu(page, 1);

View File

@@ -24,7 +24,7 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, setRealTimeMode } from '../../appActions.js';
import { waitForAnimations } from '../../baseFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - Example Imagery', () => {
@@ -32,7 +32,7 @@ test.describe('Visual - Example Imagery', () => {
let parentLayout;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
parentLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',

View File

@@ -23,14 +23,14 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - LAD Table', () => {
let ladTable;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
// Create LAD Table
ladTable = await createDomainObjectWithDefaults(page, {

View File

@@ -24,7 +24,7 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults, expandTreePaneItemByName } from '../../appActions.js';
import { expect, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { enterTextEntry, startAndAddRestrictedNotebookObject } from '../../helper/notebookUtils.js';
test.describe('Visual - Restricted Notebook @a11y', () => {
@@ -80,7 +80,7 @@ test.describe('Visual - Notebook Snapshot @a11y', () => {
test.describe('Visual - Notebook @a11y', () => {
let notebook;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
notebook = await createDomainObjectWithDefaults(page, {
type: 'Notebook',
name: 'Test Notebook'

View File

@@ -28,11 +28,11 @@ import percySnapshot from '@percy/playwright';
import { createNotification } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('Visual - Notifications @a11y', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Alert Levels and Notification List Modal', async ({ page, theme }) => {

View File

@@ -25,7 +25,7 @@ import fs from 'fs';
import { createDomainObjectWithDefaults, createPlanFromJSON } from '../../appActions.js';
import { test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import {
createTimelistWithPlanAndSetActivityInProgress,
getFirstActivity,
@@ -64,7 +64,7 @@ test.describe('Visual - Timelist progress bar @clock', () => {
test.describe('Visual - Planning', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Plan View', async ({ page, theme }) => {
@@ -83,7 +83,7 @@ test.describe('Visual - Planning', () => {
});
const newPage = await newContext.newPage();
await newPage.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await newPage.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
const plan = await createPlanFromJSON(newPage, {
name: 'Plan Visual Test',
json: examplePlanSmall2
@@ -100,7 +100,7 @@ test.describe('Visual - Planning', () => {
name: 'Plan Visual Test (Draft)',
json: examplePlanSmall2
});
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await setDraftStatusForPlan(page, plan);
await setBoundsToSpanAllActivities(page, examplePlanSmall2, plan.url);
@@ -110,7 +110,7 @@ test.describe('Visual - Planning', () => {
test.describe('Visual - Gantt Chart', () => {
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
});
test('Gantt Chart View', async ({ page, theme }) => {
const ganttChart = await createDomainObjectWithDefaults(page, {
@@ -153,7 +153,7 @@ test.describe('Visual - Gantt Chart', () => {
await setDraftStatusForPlan(page, plan);
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
await setBoundsToSpanAllActivities(page, examplePlanSmall2, ganttChart.url);
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`);

View File

@@ -28,13 +28,13 @@ import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { expect, scanForA11yViolations, test } from '../../avpFixtures.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
test.describe('Grand Search @a11y', () => {
let conditionWidget;
let displayLayout;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
displayLayout = await createDomainObjectWithDefaults(page, {
type: 'Display Layout',

View File

@@ -23,14 +23,14 @@
import percySnapshot from '@percy/playwright';
import { createDomainObjectWithDefaults } from '../../appActions.js';
import { VISUAL_URL } from '../../constants.js';
import { VISUAL_FIXED_URL } from '../../constants.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Visual - Telemetry Views', () => {
let telemetry;
test.beforeEach(async ({ page }) => {
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
// Create SWG inside of LAD Table
telemetry = await createDomainObjectWithDefaults(page, {

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import { v4 as uuid } from 'uuid';
import createExampleUser from './exampleUserCreator.js';

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
export default class SinewaveLimitProvider extends EventEmitter {
#openmct;

View File

@@ -20,26 +20,32 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global module,process*/
// eslint-disable-next-line func-style
const loadWebpackConfig = async () => {
if (process.env.KARMA_DEBUG) {
return {
config: (await import('./.webpack/webpack.dev.mjs')).default,
browsers: ['ChromeDebugging'],
singleRun: false
};
} else {
return {
config: (await import('./.webpack/webpack.coverage.mjs')).default,
browsers: ['ChromeHeadless'],
singleRun: true
};
}
};
module.exports = async (config) => {
let webpackConfig;
let browsers;
let singleRun;
if (process.env.KARMA_DEBUG) {
webpackConfig = (await import('./.webpack/webpack.dev.js')).default;
browsers = ['ChromeDebugging'];
singleRun = false;
} else {
webpackConfig = (await import('./.webpack/webpack.coverage.js')).default;
browsers = ['ChromeHeadless'];
singleRun = true;
}
const { config: webpackConfig, browsers, singleRun } = await loadWebpackConfig();
// Adjust webpack config for Karma
delete webpackConfig.output;
// karma doesn't support webpack entry
delete webpackConfig.entry;
delete webpackConfig.entry; // Karma doesn't support webpack entry
// Ensure source maps are enabled for better debugging
webpackConfig.devtool = 'inline-source-map';
config.set({
basePath: '',
@@ -106,7 +112,7 @@ module.exports = async (config) => {
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-warnings'
stats: 'detailed' // Changed to 'detailed' for more debugging info
},
concurrency: 1,
singleRun,

View File

@@ -22,62 +22,35 @@
const matcher = /\/openmct.js$/;
if (document.currentScript) {
// @ts-ignore
let src = document.currentScript.src;
if (src && matcher.test(src)) {
// eslint-disable-next-line no-undef
// @ts-ignore
__webpack_public_path__ = src.replace(matcher, '') + '/';
}
}
import { MCT } from './src/MCT.js';
const openmct = new MCT();
export default openmct;
/**
* @typedef {object} BuildInfo
* @typedef {MCT} OpenMCT
* @typedef {import('./src/api/objects/ObjectAPI').DomainObject} DomainObject
* @typedef {import('./src/api/objects/ObjectAPI').Identifier} Identifier
* @typedef {DomainObject[]} ObjectPath
* @typedef {(...args: any[]) => (openmct: OpenMCT) => void} OpenMCTPlugin
* An OpenMCT Plugin returns a function that receives an instance of
* the OpenMCT API and uses it to install itself.
* @param {OpenMCT} openmct - The Open MCT application instance.
*/
/**
* @typedef {Object} BuildInfo
* @property {string} version
* @property {string} buildDate
* @property {string} revision
* @property {string} branch
*/
/**
* @typedef {object} OpenMCT
* @property {BuildInfo} buildInfo
* @property {*} selection
* @property {import('./src/api/time/TimeAPI').default} time
* @property {import('./src/api/composition/CompositionAPI').default} composition
* @property {*} objectViews
* @property {*} inspectorViews
* @property {*} propertyEditors
* @property {*} toolbars
* @property {import('./src/api/types/TypeRegistry').default} types
* @property {import('./src/api/objects/ObjectAPI').default} objects
* @property {import('./src/api/telemetry/TelemetryAPI').default} telemetry
* @property {import('./src/api/indicators/IndicatorAPI').default} indicators
* @property {import('./src/api/user/UserAPI').default} user
* @property {import('./src/api/notifications/NotificationAPI').default} notifications
* @property {import('./src/api/Editor').default} editor
* @property {import('./src/api/overlays/OverlayAPI')} overlays
* @property {import('./src/api/tooltips/ToolTipAPI')} tooltips
* @property {import('./src/api/menu/MenuAPI').default} menus
* @property {import('./src/api/actions/ActionsAPI').default} actions
* @property {import('./src/api/status/StatusAPI').default} status
* @property {*} priority
* @property {import('./src/ui/router/ApplicationRouter')} router
* @property {import('./src/api/faultmanagement/FaultManagementAPI').default} faults
* @property {import('./src/api/forms/FormsAPI').default} forms
* @property {import('./src/api/Branding').default} branding
* @property {import('./src/api/annotation/AnnotationAPI').default} annotation
* @property {{(plugin: OpenMCTPlugin) => void}} install
* @property {{() => string}} getAssetPath
* @property {{(assetPath: string) => void}} setAssetPath
* @property {{(domElement: HTMLElement, isHeadlessMode: boolean) => void}} start
* @property {{() => void}} startHeadless
* @property {{() => void}} destroy
* @property {OpenMCTPlugin[]} plugins
* @property {OpenMCTComponent[]} components
*/
import { MCT } from './src/MCT.js';
/** @type {OpenMCT} */
const openmct = new MCT();
export default openmct;

470
package-lock.json generated
View File

@@ -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",
@@ -80,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",
@@ -98,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",
@@ -1495,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"
@@ -1904,6 +1915,12 @@
"@types/node": "*"
}
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
"integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
"dev": true
},
"node_modules/@types/sockjs": {
"version": "0.3.36",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
@@ -1922,217 +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/@typescript-eslint/parser": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -5635,9 +5441,9 @@
}
},
"node_modules/express": {
"version": "4.18.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
"integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dev": true,
"dependencies": {
"accepts": "~1.3.8",
@@ -5645,7 +5451,7 @@
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -5677,9 +5483,9 @@
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"engines": {
"node": ">= 0.6"
@@ -8743,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",
@@ -9255,6 +9065,207 @@
}
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/scope-manager": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/prettier-eslint/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/prettier-eslint/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/prettier-eslint/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/prettier-eslint/node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/prettier-eslint/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prettier-eslint/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/prettier-eslint/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prettier-eslint/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/prettier-eslint/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
@@ -11635,14 +11646,15 @@
}
},
"node_modules/webpack-dev-middleware": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.0.0.tgz",
"integrity": "sha512-tZ5hqsWwww/8DislmrzXE3x+4f+v10H1z57mA2dWFrILb4i3xX+dPhTkcdR0DLyQztrhF2AUmO5nN085UYjd/Q==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.1.1.tgz",
"integrity": "sha512-NmRVq4AvRQs66dFWyDR4GsFDJggtSi2Yn38MXLk0nffgF9n/AIP4TFBg2TQKYaRAN4sHuKOTiz9BnNCENDLEVA==",
"dev": true,
"dependencies": {
"colorette": "^2.0.10",
"memfs": "^4.6.0",
"mime-types": "^2.1.31",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"schema-utils": "^4.0.0"
},

View File

@@ -2,19 +2,25 @@
"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-shape": "3.0.0",
"@types/d3-scale": "4.0.8",
"@types/d3-selection": "3.0.10",
"@types/d3-shape": "3.0.0",
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "5.1.2",
"@types/lodash": "4.17.0",
@@ -27,9 +33,9 @@
"cspell": "7.3.8",
"css-loader": "6.10.0",
"d3-axis": "3.0.0",
"d3-shape": "3.0.0",
"d3-scale": "4.0.2",
"d3-selection": "3.0.0",
"d3-shape": "3.0.0",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-compat": "4.2.0",
@@ -76,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",
@@ -91,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",

View File

@@ -19,8 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* eslint-disable no-undef */
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import { createApp, markRaw } from 'vue';
import ActionsAPI from './api/actions/ActionsAPI.js';
@@ -73,37 +72,47 @@ import Browse from './ui/router/Browse.js';
* The Open MCT application. This may be configured by installing plugins
* or registering extensions before the application is started.
* @constructor
* @memberof module:openmct
*/
export class MCT extends EventEmitter {
/**
* @type {import('openmct.js').BuildInfo}
*/
buildInfo;
/**
* @type {string}
*/
defaultClock;
/**
* @type {Record<string, OpenMCTPlugin>}
*/
plugins;
/**
* Tracks current selection state of the application.
* @type {Selection}
*/
selection;
constructor() {
super();
EventEmitter.call(this);
this.buildInfo = {
// @ts-ignore
version: __OPENMCT_VERSION__,
// @ts-ignore
buildDate: __OPENMCT_BUILD_DATE__,
// @ts-ignore
revision: __OPENMCT_REVISION__,
// @ts-ignore
branch: __OPENMCT_BUILD_BRANCH__
};
this.destroy = this.destroy.bind(this);
this.defaultClock = 'local';
this.plugins = plugins;
/**
* Tracks current selection state of the application.
* @private
*/
this.selection = new Selection(this);
/**
* MCT's time conductor, which may be used to synchronize view contents
* for telemetry- or time-based views.
* @type {module:openmct.TimeConductor}
* @memberof module:openmct.MCT#
* @name conductor
* @type {TimeAPI}
*/
this.time = new TimeAPI(this);
@@ -116,9 +125,7 @@ export class MCT extends EventEmitter {
* `composition` may be called as a function, in which case it acts
* as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
*
* @type {module:openmct.CompositionAPI}
* @memberof module:openmct.MCT#
* @name composition
* @type {CompositionAPI}
*/
this.composition = new CompositionAPI(this);
@@ -126,9 +133,7 @@ export class MCT extends EventEmitter {
* Registry for views of domain objects which should appear in the
* main viewing area.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name objectViews
* @type {ViewRegistry}
*/
this.objectViews = new ViewRegistry();
@@ -136,9 +141,7 @@ export class MCT extends EventEmitter {
* Registry for views which should appear in the Inspector area.
* These views will be chosen based on the selection state.
*
* @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT#
* @name inspectorViews
* @type {InspectorViewRegistry}
*/
this.inspectorViews = new InspectorViewRegistry();
@@ -147,9 +150,7 @@ export class MCT extends EventEmitter {
* dialogs, and similar user interface elements used for
* modifying domain objects external to its regular views.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name propertyEditors
* @type {ViewRegistry}
*/
this.propertyEditors = new ViewRegistry();
@@ -157,9 +158,7 @@ export class MCT extends EventEmitter {
* Registry for views which should appear in the toolbar area while
* editing. These views will be chosen based on the selection state.
*
* @type {module:openmct.ToolbarRegistry}
* @memberof module:openmct.MCT#
* @name toolbars
* @type {ToolbarRegistry}
*/
this.toolbars = new ToolbarRegistry();
@@ -167,9 +166,7 @@ export class MCT extends EventEmitter {
* Registry for domain object types which may exist within this
* instance of Open MCT.
*
* @type {module:openmct.TypeRegistry}
* @memberof module:openmct.MCT#
* @name types
* @type {TypeRegistry}
*/
this.types = new TypeRegistry();
@@ -177,9 +174,7 @@ export class MCT extends EventEmitter {
* An interface for interacting with domain objects and the domain
* object hierarchy.
*
* @type {module:openmct.ObjectAPI}
* @memberof module:openmct.MCT#
* @name objects
* @type {ObjectAPI}
*/
this.objects = new ObjectAPI(this.types, this);
@@ -187,49 +182,100 @@ export class MCT extends EventEmitter {
* An interface for retrieving and interpreting telemetry data associated
* with a domain object.
*
* @type {module:openmct.TelemetryAPI}
* @memberof module:openmct.MCT#
* @name telemetry
* @type {TelemetryAPI}
*/
this.telemetry = new TelemetryAPI(this);
/**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
* @type {IndicatorAPI}
*/
this.indicators = new IndicatorAPI(this);
/**
* MCT's user awareness management, to enable user and
* role specific functionality.
* @type {module:openmct.UserAPI}
* @memberof module:openmct.MCT#
* @name user
* @type {UserAPI}
*/
this.user = new UserAPI(this);
/**
* An interface for managing notifications and alerts.
* @type {NotificationAPI}
*/
this.notifications = new NotificationAPI();
/**
* An interface for editing domain objects.
* @type {EditorAPI}
*/
this.editor = new EditorAPI(this);
/**
* An interface for managing overlays.
* @type {OverlayAPI}
*/
this.overlays = new OverlayAPI();
/**
* An interface for managing tooltips.
* @type {ToolTipAPI}
*/
this.tooltips = new ToolTipAPI();
/**
* An interface for managing menus.
* @type {MenuAPI}
*/
this.menus = new MenuAPI(this);
/**
* An interface for managing menu actions.
* @type {ActionsAPI}
*/
this.actions = new ActionsAPI(this);
/**
* An interface for managing statuses.
* @type {StatusAPI}
*/
this.status = new StatusAPI(this);
/**
* An object defining constants for priority levels.
* @type {PriorityAPI}
*/
this.priority = PriorityAPI;
/**
* An interface for routing application traffic.
* @type {ApplicationRouter}
*/
this.router = new ApplicationRouter(this);
/**
* An interface for managing faults.
* @type {FaultManagementAPI}
*/
this.faults = new FaultManagementAPI(this);
/**
* An interface for managing forms.
* @type {FormsAPI}
*/
this.forms = new FormsAPI(this);
/**
* An interface for branding the application.
* @type {BrandingAPI}
*/
this.branding = BrandingAPI;
/**
* MCT's annotation API that enables
* human-created comments and categorization linked to data products
* @type {module:openmct.AnnotationAPI}
* @memberof module:openmct.MCT#
* @name annotation
* @type {AnnotationAPI}
*/
this.annotation = new AnnotationAPI(this);
@@ -297,7 +343,7 @@ export class MCT extends EventEmitter {
* @fires module:openmct.MCT~start
* @memberof module:openmct.MCT#
* @method start
* @param {HTMLElement} [domElement] the DOM element in which to run
* @param {Element?} domElement the DOM element in which to run
* MCT; if undefined, MCT will be run in the body of the document
*/
start(domElement = document.body.firstElementChild, isHeadlessMode = false) {
@@ -373,3 +419,7 @@ export class MCT extends EventEmitter {
this.router.destroy();
}
}
/**
* @typedef {import('../openmct.js').OpenMCTPlugin} OpenMCTPlugin
*/

View File

@@ -23,7 +23,7 @@
let brandingOptions = {};
/**
* @typedef {object} BrandingOptions
* @typedef {Object} BrandingOptions
* @property {string} smallLogoImage URL to the image to use as the applications logo.
* This logo will appear on every screen and when clicked will launch the about dialog.
* @property {string} aboutHtml Custom content for the about screen. When defined the

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
export default class Editor extends EventEmitter {
constructor(openmct) {

View File

@@ -20,10 +20,18 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import _ from 'lodash';
class ActionCollection extends EventEmitter {
/**
*
* @param {any[]} applicableActions
* @param {import('openmct').ObjectPath} objectPath
* @param {any} view
* @param {import('openmct').OpenMCT} openmct
* @param {boolean} skipEnvironmentObservers
*/
constructor(applicableActions, objectPath, view, openmct, skipEnvironmentObservers) {
super();

View File

@@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import _ from 'lodash';
import ActionCollection from './ActionCollection.js';

View File

@@ -20,18 +20,18 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import _ from 'lodash';
import { v4 as uuid } from 'uuid';
/**
* @readonly
* @enum {String} AnnotationType
* @property {String} NOTEBOOK The notebook annotation type
* @property {String} GEOSPATIAL The geospatial annotation type
* @property {String} PIXEL_SPATIAL The pixel-spatial annotation type
* @property {String} TEMPORAL The temporal annotation type
* @property {String} PLOT_SPATIAL The plot-spatial annotation type
* @enum {string} AnnotationType
* @property {string} NOTEBOOK The notebook annotation type
* @property {string} GEOSPATIAL The geospatial annotation type
* @property {string} PIXEL_SPATIAL The pixel-spatial annotation type
* @property {string} TEMPORAL The temporal annotation type
* @property {string} PLOT_SPATIAL The plot-spatial annotation type
*/
const ANNOTATION_TYPES = Object.freeze({
NOTEBOOK: 'NOTEBOOK',
@@ -47,9 +47,9 @@ const ANNOTATION_LAST_CREATED = 'annotationLastCreated';
/**
* @typedef {Object} Tag
* @property {String} key a unique identifier for the tag
* @property {String} backgroundColor eg. "#cc0000"
* @property {String} foregroundColor eg. "#ffffff"
* @property {string} key a unique identifier for the tag
* @property {string} backgroundColor eg. "#cc0000"
* @property {string} foregroundColor eg. "#ffffff"
*/
/**
@@ -80,7 +80,7 @@ export default class AnnotationAPI extends EventEmitter {
#targetComparatorMap;
/**
* @param {OpenMCT} openmct
* @param {MCT} openmct
*/
constructor(openmct) {
super();
@@ -112,11 +112,11 @@ export default class AnnotationAPI extends EventEmitter {
/**
* Creates an annotation on a given domain object (e.g., a plot) and a set of targets (e.g., telemetry objects)
* @typedef {Object} CreateAnnotationOptions
* @property {String} name a name for the new annotation (e.g., "Plot annnotation")
* @property {string} name a name for the new annotation (e.g., "Plot annnotation")
* @property {DomainObject} domainObject the domain object this annotation was created with
* @property {ANNOTATION_TYPES} annotationType the type of annotation to create (e.g., PLOT_SPATIAL)
* @property {Tag[]} tags tags to add to the annotation, e.g., SCIENCE for science related annotations
* @property {String} contentText Some text to add to the annotation, e.g. ("This annotation is about science")
* @property {string} contentText Some text to add to the annotation, e.g. ("This annotation is about science")
* @property {Array<Object>} targets The targets ID keystrings and their specific properties.
* For plots, this will be a bounding box, e.g.: {keyString: "d8385009-789d-457b-acc7-d50ba2fd55ea", maxY: 100, minY: 0, maxX: 100, minX: 0}
* For notebooks, this will be an entry ID, e.g.: {entryId: "entry-ecb158f5-d23c-45e1-a704-649b382622ba"}
@@ -208,7 +208,7 @@ export default class AnnotationAPI extends EventEmitter {
/**
* @method defineTag
* @param {String} key a unique identifier for the tag
* @param {string} key a unique identifier for the tag
* @param {Tag} tagsDefinition the definition of the tag to add
*/
defineTag(tagKey, tagsDefinition) {
@@ -217,7 +217,7 @@ export default class AnnotationAPI extends EventEmitter {
/**
* @method setNamespaceToSaveAnnotations
* @param {String} namespace the namespace to save new annotations to
* @param {string} namespace the namespace to save new annotations to
*/
setNamespaceToSaveAnnotations(namespace) {
this.namespaceToSaveAnnotations = namespace;
@@ -226,7 +226,7 @@ export default class AnnotationAPI extends EventEmitter {
/**
* @method isAnnotation
* @param {DomainObject} domainObject the domainObject in question
* @returns {Boolean} Returns true if the domain object is an annotation
* @returns {boolean} Returns true if the domain object is an annotation
*/
isAnnotation(domainObject) {
return domainObject && domainObject.type === ANNOTATION_TYPE;
@@ -442,7 +442,7 @@ export default class AnnotationAPI extends EventEmitter {
/**
* @method searchForTags
* @param {String} query A query to match against tags. E.g., "dr" will match the tags "drilling" and "driving"
* @param {string} query A query to match against tags. E.g., "dr" will match the tags "drilling" and "driving"
* @param {Object} [abortController] An optional abort method to stop the query
* @returns {Promise} returns a model of matching tags with their target domain objects attached
*/

View File

@@ -72,7 +72,7 @@ export default class CompositionAPI {
*
* @method get
* @param {DomainObject} domainObject
* @returns {CompositionCollection}
* @returns {CompositionCollection | undefined}
*/
get(domainObject) {
const provider = this.registry.find((p) => {

View File

@@ -33,7 +33,7 @@
*/
/**
* @typedef {object} ListenerMap
* @typedef {Object} ListenerMap
* @property {Array.<any>} add
* @property {Array.<any>} remove
* @property {Array.<any>} load
@@ -200,7 +200,7 @@ export default class CompositionCollection {
/**
* Load the domain objects in this composition.
*
* @param {AbortSignal} abortSignal
* @param {AbortSignal} [abortSignal]
* @returns {Promise.<Array.<DomainObject>>} a promise for
* the domain objects in this composition
* @memberof {module:openmct.CompositionCollection#}
@@ -271,7 +271,7 @@ export default class CompositionCollection {
/**
* Handle reorder from provider.
* @private
* @param {object} reorderMap
* @param {Object} reorderMap
*/
#onProviderReorder(reorderMap) {
this.#emit('reorder', reorderMap);

View File

@@ -24,11 +24,11 @@ import _ from 'lodash';
import { makeKeyString, parseKeyString } from '../objects/object-utils.js';
/**
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
* @typedef {import('openmct').DomainObject} DomainObject
*/
/**
* @typedef {import('../objects/ObjectAPI').Identifier} Identifier
* @typedef {import('openmct').Identifier} Identifier
*/
/**
@@ -36,7 +36,7 @@ import { makeKeyString, parseKeyString } from '../objects/object-utils.js';
*/
/**
* @typedef {import('../../../openmct').OpenMCT} OpenMCT
* @typedef {import('openmct').OpenMCT} OpenMCT
*/
/**
@@ -84,7 +84,7 @@ export default class CompositionProvider {
* Check if this provider should be used to load composition for a
* particular domain object.
* @method appliesTo
* @param {import('../objects/ObjectAPI').DomainObject} domainObject the domain object
* @param {DomainObject} domainObject the domain object
* to check
* @returns {boolean} true if this provider can provide composition for a given domain object
*/
@@ -98,7 +98,6 @@ export default class CompositionProvider {
* for which to load composition
* @returns {Promise<Identifier[]>} a promise for
* the Identifiers in this composition
* @method load
*/
load(domainObject) {
throw new Error('This method must be implemented by a subclass.');
@@ -137,7 +136,6 @@ export default class CompositionProvider {
* @param {DomainObject} domainObject the domain object
* which should have its composition modified
* @param {Identifier} childId the domain object to remove
* @method remove
*/
remove(domainObject, childId) {
throw new Error('This method must be implemented by a subclass.');
@@ -151,7 +149,6 @@ export default class CompositionProvider {
* @param {DomainObject} parent the domain object
* which should have its composition modified
* @param {Identifier} childId the domain object to add
* @method add
*/
add(parent, childId) {
throw new Error('This method must be implemented by a subclass.');
@@ -179,7 +176,6 @@ export default class CompositionProvider {
/**
* Listens on general mutation topic, using injector to fetch to avoid
* circular dependencies.
* @private
*/
#establishTopicListener() {
if (this.topicListener) {
@@ -194,7 +190,6 @@ export default class CompositionProvider {
}
/**
* @private
* @param {DomainObject} parent
* @param {DomainObject} child
* @returns {boolean}
@@ -207,7 +202,6 @@ export default class CompositionProvider {
}
/**
* @private
* @param {DomainObject} parent
* @returns {boolean}
*/
@@ -219,7 +213,6 @@ export default class CompositionProvider {
* Handles mutation events. If there are active listeners for the mutated
* object, detects changes to composition and triggers necessary events.
*
* @private
* @param {DomainObject} oldDomainObject
*/
#onMutation(newDomainObject, oldDomainObject) {
@@ -230,6 +223,10 @@ export default class CompositionProvider {
return;
}
if (oldDomainObject.composition === undefined || newDomainObject.composition === undefined) {
return;
}
const oldComposition = oldDomainObject.composition.map(makeKeyString);
const newComposition = newDomainObject.composition.map(makeKeyString);

View File

@@ -88,21 +88,21 @@ export default class FaultManagementAPI {
}
/**
* @typedef {object} TriggerValueInfo
* @typedef {Object} TriggerValueInfo
* @property {number} value
* @property {string} rangeCondition
* @property {string} monitoringResult
*/
/**
* @typedef {object} CurrentValueInfo
* @typedef {Object} CurrentValueInfo
* @property {number} value
* @property {string} rangeCondition
* @property {string} monitoringResult
*/
/**
* @typedef {object} Fault
* @typedef {Object} Fault
* @property {boolean} acknowledged
* @property {CurrentValueInfo} currentValueInfo
* @property {string} id
@@ -117,7 +117,7 @@ export default class FaultManagementAPI {
*/
/**
* @typedef {object} FaultAPIResponse
* @typedef {Object} FaultAPIResponse
* @property {string} type
* @property {Fault} fault
*/

View File

@@ -48,7 +48,7 @@ export default class FormsAPI {
* this formControlViewProvider is used inside form overlay to show/render a form row
*
* @public
* @param {String} controlName a form structure, array of section
* @param {string} controlName a form structure, array of section
* @param {ControlViewProvider} controlViewProvider
*/
addNewFormControl(controlName, controlViewProvider) {
@@ -59,7 +59,7 @@ export default class FormsAPI {
* Get a ControlViewProvider for a given/stored form controlName
*
* @public
* @param {String} controlName a form structure, array of section
* @param {string} controlName a form structure, array of section
* @return {ControlViewProvider}
*/
getFormControl(controlName) {
@@ -69,7 +69,7 @@ export default class FormsAPI {
/**
* Section definition for formStructure
* @typedef Section
* @property {object} name Name of the section to display on Form
* @property {Object} name Name of the section to display on Form
* @property {string} cssClass class name for styling section
* @property {array<Row>} rows collection of rows inside a section
*/

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import vueWrapHtmlElement from '../../utils/vueWrapHtmlElement.js';
import SimpleIndicator from './SimpleIndicator.js';

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import { convertTemplateToHTML } from '@/utils/template/templateHelpers';

View File

@@ -25,7 +25,7 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
/**
* Popup Menu options
* @typedef {Object} MenuOptions
* @property {String} menuClass Class for popup menu
* @property {string} menuClass Class for popup menu
* @property {MENU_PLACEMENT} placement Placement for menu relative to click
* @property {Function} onDestroy callback function: invoked when menu is destroyed
*/
@@ -33,10 +33,10 @@ import Menu, { MENU_PLACEMENT } from './menu.js';
/**
* Popup Menu Item/action
* @typedef {Object} Action
* @property {String} cssClass Class for menu item
* @property {Boolean} isDisabled adds disable class if true
* @property {String} name Menu item text
* @property {String} description Menu item description
* @property {string} cssClass Class for menu item
* @property {boolean} isDisabled adds disable class if true
* @property {string} name Menu item text
* @property {string} description Menu item description
* @property {Function} onItemClicked callback function: invoked when item is clicked
*/

View File

@@ -19,7 +19,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import mount from 'utils/mount';
import { h } from 'vue';

View File

@@ -34,7 +34,7 @@ import EventEmitter from 'eventemitter3';
import moment from 'moment';
/**
* @typedef {object} NotificationProperties
* @typedef {Object} NotificationProperties
* @property {function} dismiss Dismiss the notification
* @property {NotificationModel} model The Notification model
* @property {(progressPerc: number, progressText: string) => void} [progress] Update the progress of the notification
@@ -45,14 +45,14 @@ import moment from 'moment';
*/
/**
* @typedef {object} NotificationLink
* @typedef {Object} NotificationLink
* @property {function} onClick The function to be called when the link is clicked
* @property {string} cssClass A CSS class name to style the link
* @property {string} text The text to be displayed for the link
*/
/**
* @typedef {object} NotificationOptions
* @typedef {Object} NotificationOptions
* @property {number} [autoDismissTimeout] Milliseconds to wait before automatically dismissing the notification
* @property {boolean} [minimized] Allows for a notification to be minimized into the indicator by default
* @property {NotificationLink} [link] A link for the notification
@@ -66,7 +66,7 @@ import moment from 'moment';
* and then minimized to a banner notification if needed, or vice-versa.
*
* @see DialogModel
* @typedef {object} NotificationModel
* @typedef {Object} NotificationModel
* @property {string} message The message to be displayed by the notification
* @property {number | 'unknown'} [progress] The progress of some ongoing task. Should be a number between 0 and 100, or
* with the string literal 'unknown'.

View File

@@ -391,7 +391,7 @@ class InMemorySearchProvider {
* Dispatch a search query to the worker and return a queryId.
*
* @private
* @returns {String} a unique query Id for the query.
* @returns {string} a unique query Id for the query.
*/
#dispatchSearchToWorker({ queryId, searchType, query, maxResults }) {
const message = {

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import { identifierEquals, makeKeyString, parseKeyString, refresh } from 'objectUtils';
import ConflictError from './ConflictError.js';
@@ -33,39 +33,21 @@ import Transaction from './Transaction.js';
/**
* Uniquely identifies a domain object.
*
* @typedef {object} Identifier
* @property {string} namespace the namespace to/from which this domain
* object should be loaded/stored.
* @property {string} key a unique identifier for the domain object
* within that namespace
* @memberof module:openmct.ObjectAPI~
* @typedef {Object} Identifier
* @property {string} namespace the namespace to/from which this domain object should be loaded/stored.
* @property {string} key a unique identifier for the domain object within that namespace
*/
/**
* A domain object is an entity of relevance to a user's workflow, that
* should appear as a distinct and meaningful object within the user
* interface. Examples of domain objects are folders, telemetry sensors,
* and so forth.
*
* A few common properties are defined for domain objects. Beyond these,
* individual types of domain objects may add more as they see fit.
*
* @typedef {object} DomainObject
* @property {Identifier} identifier a key/namespace pair which
* uniquely identifies this domain object
* A domain object is an entity of relevance to a user's workflow, that should appear as a distinct and meaningful object within the user interface.
* @typedef {Object} DomainObject
* @property {Identifier} identifier a key/namespace pair which uniquely identifies this domain object
* @property {string} type the type of domain object
* @property {string} name the human-readable name for this domain object
* @property {string} [creator] the user name of the creator of this domain
* object
* @property {number} [modified] the time, in milliseconds since the UNIX
* epoch, at which this domain object was last modified
* @property {Identifier[]} [composition] if
* present, this will be used by the default composition provider
* to load domain objects
* @property {Object.<string, any>} [configuration] A key-value map containing configuration
* settings for this domain object.
* @memberof module:openmct.ObjectAPI~
* @property {string} [creator] the user name of the creator of this domain object
* @property {number} [modified] the time, in milliseconds since the UNIX epoch, at which this domain object was last modified
* @property {Identifier[]} [composition] if present, this will be used by the default composition provider to load domain objects
* @property {Record<string, any>} [configuration] A key-value map containing configuration settings for this domain object.
*/
/**
@@ -78,8 +60,6 @@ import Transaction from './Transaction.js';
/**
* Utilities for loading, saving, and manipulating domain objects.
* @interface ObjectAPI
* @memberof module:openmct
*/
export default class ObjectAPI {
#makeKeyString;
@@ -88,6 +68,10 @@ export default class ObjectAPI {
#refresh;
#openmct;
/**
* @param {any} typeRegistry
* @param {any} openmct
*/
constructor(typeRegistry, openmct) {
this.#makeKeyString = makeKeyString;
this.#parseKeyString = parseKeyString;
@@ -125,6 +109,8 @@ export default class ObjectAPI {
/**
* Retrieve the provider for a given identifier.
* @param {Identifier} identifier
* @returns {ObjectProvider | RootObjectProvider}
*/
getProvider(identifier) {
if (identifier.key === 'ROOT') {
@@ -144,7 +130,7 @@ export default class ObjectAPI {
/**
* Get the root-level object.
* @returns {Promise.<DomainObject>} a promise for the root object
* @returns {Promise<DomainObject>} a promise for the root object
*/
getRoot() {
return this.rootProvider.get();
@@ -154,63 +140,17 @@ export default class ObjectAPI {
* Register a new object provider for a particular namespace.
*
* @param {string} namespace the namespace for which to provide objects
* @param {module:openmct.ObjectProvider} provider the provider which
* will handle loading domain objects from this namespace
* @memberof {module:openmct.ObjectAPI#}
* @name addProvider
* @param {ObjectProvider} provider the provider which will handle loading domain objects from this namespace
*/
addProvider(namespace, provider) {
this.providers[namespace] = provider;
}
/**
* Provides the ability to read, write, and delete domain objects.
*
* When registering a new object provider, all methods on this interface
* are optional.
*
* @interface ObjectProvider
* @memberof module:openmct
*/
/**
* Create the given domain object in the corresponding persistence store
*
* @method create
* @memberof module:openmct.ObjectProvider#
* @param {module:openmct.DomainObject} domainObject the domain object to
* create
* @returns {Promise} a promise which will resolve when the domain object
* has been created, or be rejected if it cannot be saved
*/
/**
* Update this domain object in its persistence store
*
* @method update
* @memberof module:openmct.ObjectProvider#
* @param {module:openmct.DomainObject} domainObject the domain object to
* update
* @returns {Promise} a promise which will resolve when the domain object
* has been updated, or be rejected if it cannot be saved
*/
/**
* Delete this domain object.
*
* @method delete
* @memberof module:openmct.ObjectProvider#
* @param {module:openmct.DomainObject} domainObject the domain object to
* delete
* @returns {Promise} a promise which will resolve when the domain object
* has been deleted, or be rejected if it cannot be deleted
*/
/**
* Get a domain object.
*
* @param {string} key the key for the domain object to load
* @param {AbortSignal} abortSignal (optional) signal to abort fetch requests
* @param {Identifier | string} identifier the identifier for the domain object to load
* @param {AbortSignal} [abortSignal] (optional) signal to abort fetch requests
* @param {boolean} [forceRemote=false] defaults to false. If true, will skip cached and
* dirty/in-transaction objects use and the provider.get method
* @returns {Promise<DomainObject>} a promise which will resolve when the domain object
@@ -289,14 +229,10 @@ export default class ObjectAPI {
* and will be searched using the fallback in-memory search.
* Search results are asynchronous and resolve in parallel.
*
* @method search
* @memberof module:openmct.ObjectAPI#
* @param {string} query the term to search for
* @param {AbortController.signal} abortSignal (optional) signal to cancel downstream fetch requests
* @param {string} searchType the type of search as defined by SEARCH_TYPES
* @returns {Array.<Promise.<module:openmct.DomainObject>>}
* an array of promises returned from each object provider's search function
* each resolving to domain objects matching provided search query and options.
* @param {AbortController.signal} [abortSignal] (optional) signal to cancel downstream fetch requests
* @param {string} [searchType=this.SEARCH_TYPES.OBJECTS] the type of search as defined by SEARCH_TYPES
* @returns {Promise<DomainObject>[]} an array of promises returned from each object provider's search function, each resolving to domain objects matching the provided search query and options
*/
search(query, abortSignal, searchType = this.SEARCH_TYPES.OBJECTS) {
if (!Object.keys(this.SEARCH_TYPES).includes(searchType.toUpperCase())) {
@@ -330,9 +266,8 @@ export default class ObjectAPI {
* platform will manage the lifecycle of any mutable objects that it provides. If you use `getMutable` you are
* committing to managing that lifecycle yourself. `.destroy` should be called when the object is no longer needed.
*
* @memberof {module:openmct.ObjectAPI#}
* @returns {Promise.<MutableDomainObject>} a promise that will resolve with a MutableDomainObject if
* the object can be mutated.
* @param {Identifier} identifier the identifier of the object to fetch
* @returns {Promise<MutableDomainObject>} a promise that will resolve with a MutableDomainObject if the object can be mutated
*/
getMutable(identifier) {
if (!this.supportsMutation(identifier)) {
@@ -348,7 +283,7 @@ export default class ObjectAPI {
* This function is for cleaning up a mutable domain object when you're done with it.
* You only need to use this if you retrieved the object using `getMutable()`. If the object was provided by the
* platform (eg. passed into a `view()` function) then the platform is responsible for its lifecycle.
* @param {MutableDomainObject} domainObject
* @param {MutableDomainObject} domainObject the mutable domain object to destroy
*/
destroyMutable(domainObject) {
if (domainObject.isMutable) {
@@ -382,11 +317,8 @@ export default class ObjectAPI {
/**
* Save this domain object in its current state.
*
* @memberof module:openmct.ObjectAPI#
* @param {module:openmct.DomainObject} domainObject the domain object to
* save
* @returns {Promise} a promise which will resolve when the domain object
* has been saved, or be rejected if it cannot be saved
* @param {DomainObject} domainObject the domain object to save
* @returns {Promise} a promise which will resolve when the domain object has been saved, or be rejected if it cannot be saved
*/
async save(domainObject) {
const provider = this.getProvider(domainObject.identifier);
@@ -538,7 +470,6 @@ export default class ObjectAPI {
/**
* Retrieve the interceptors for a given domain object.
* @private
*/
#listGetInterceptors(identifier, object) {
return this.interceptorRegistry.getInterceptors(identifier, object);
@@ -560,7 +491,7 @@ export default class ObjectAPI {
/**
* Return relative url path from a given object path
* eg: #/browse/mine/cb56f6bf-c900-43b7-b923-2e3b64b412db/6e89e858-77ce-46e4-a1ad-749240286497/....
* @param {Array} objectPath
* @param {Array<DomainObject>} objectPath
* @returns {string} relative url for object
*/
getRelativePath(objectPath) {
@@ -572,8 +503,8 @@ export default class ObjectAPI {
/**
* Return path of telemetry objects in the object composition
* @param {object} identifier the identifier for the domain object to query for
* @param {object} [telemetryIdentifier] the specific identifier for the telemetry
* @param {Object} identifier the identifier for the domain object to query for
* @param {Object} [telemetryIdentifier] the specific identifier for the telemetry
* to look for in the composition, uses first object in composition otherwise
* @returns {Array} path of telemetry object in object composition
*/
@@ -612,13 +543,10 @@ export default class ObjectAPI {
/**
* Modify a domain object. Internal to ObjectAPI, won't call save after.
* @private
*
* @param {module:openmct.DomainObject} object the object to mutate
* @param {DomainObject} domainObject the object to mutate
* @param {string} path the property to modify
* @param {*} value the new value for this property
* @method mutate
* @memberof module:openmct.ObjectAPI#
*/
#mutate(domainObject, path, value) {
if (!this.supportsMutation(domainObject.identifier)) {
@@ -628,28 +556,26 @@ export default class ObjectAPI {
if (domainObject.isMutable) {
domainObject.$set(path, value);
} else {
//Creating a temporary mutable domain object allows other mutable instances of the
//object to be kept in sync.
// Creating a temporary mutable domain object allows other mutable instances of the
// object to be kept in sync.
let mutableDomainObject = this.toMutable(domainObject);
//Mutate original object
// Mutate original object
MutableDomainObject.mutateObject(domainObject, path, value);
//Mutate temporary mutable object, in the process informing any other mutable instances
// Mutate temporary mutable object, in the process informing any other mutable instances
mutableDomainObject.$set(path, value);
//Destroy temporary mutable object
// Destroy temporary mutable object
this.destroyMutable(mutableDomainObject);
}
}
/**
* Modify a domain object and save.
* @param {module:openmct.DomainObject} object the object to mutate
* @param {DomainObject} domainObject the object to mutate
* @param {string} path the property to modify
* @param {*} value the new value for this property
* @method mutate
* @memberof module:openmct.ObjectAPI#
*/
mutate(domainObject, path, value) {
this.#mutate(domainObject, path, value);
@@ -662,11 +588,9 @@ export default class ObjectAPI {
}
/**
* Create a mutable domain object from an existing domain object
* @param {module:openmct.DomainObject} domainObject the object to make mutable
* Create a mutable domain object from an existing domain object.
* @param {DomainObject} domainObject the object to make mutable
* @returns {MutableDomainObject} a mutable domain object that will automatically sync
* @method toMutable
* @memberof module:openmct.ObjectAPI#
*/
toMutable(domainObject) {
let mutableObject;
@@ -689,8 +613,8 @@ export default class ObjectAPI {
// modified can sometimes be undefined, so make it 0 in this case
const mutableObjectModification = mutableObject.modified ?? Number.MIN_SAFE_INTEGER;
if (updatedModel.persisted > mutableObjectModification) {
//Don't replace with a stale model. This can happen on slow connections when multiple mutations happen
//in rapid succession and intermediate persistence states are returned by the observe function.
// Don't replace with a stale model. This can happen on slow connections when multiple mutations happen
// in rapid succession and intermediate persistence states are returned by the observe function.
updatedModel = this.applyGetInterceptors(identifier, updatedModel);
mutableObject.$refresh(updatedModel);
}
@@ -706,10 +630,10 @@ export default class ObjectAPI {
/**
* Updates a domain object based on its latest persisted state. Note that this will mutate the provided object.
* @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store
* @param {DomainObject} domainObject an object to refresh from its persistence store
* @param {boolean} [forceRemote=false] defaults to false. If true, will skip cached and
* dirty/in-transaction objects use and the provider.get method
* @returns {Promise} the provided object, updated to reflect the latest persisted state of the object.
* @returns {Promise<DomainObject>} the provided object, updated to reflect the latest persisted state of the object.
*/
async refresh(domainObject, forceRemote = false) {
const refreshedObject = await this.get(domainObject.identifier, null, forceRemote);
@@ -724,7 +648,8 @@ export default class ObjectAPI {
}
/**
* @param module:openmct.ObjectAPI~Identifier identifier An object identifier
* Determine if the object can be mutated.
* @param {Identifier} identifier An object identifier
* @returns {boolean} true if the object can be mutated, otherwise returns false
*/
supportsMutation(identifier) {
@@ -733,12 +658,10 @@ export default class ObjectAPI {
/**
* Observe changes to a domain object.
* @param {module:openmct.DomainObject} object the object to observe
* @param {DomainObject} domainObject the object to observe
* @param {string} path the property to observe
* @param {Function} callback a callback to invoke when new values for
* this property are observed.
* @method observe
* @memberof module:openmct.ObjectAPI#
* @param {Function} callback a callback to invoke when new values for this property are observed.
* @returns {() => void} a function to unsubscribe from the updates
*/
observe(domainObject, path, callback) {
if (domainObject.isMutable) {
@@ -785,7 +708,7 @@ export default class ObjectAPI {
/**
* Given an original path check if the path is reachable via root
* @param {Array<Object>} originalPath an array of path objects to check
* @param {Array<DomainObject>} originalPath an array of path objects to check
* @returns {boolean} whether the domain object is reachable
*/
isReachable(originalPath) {
@@ -796,6 +719,12 @@ export default class ObjectAPI {
return false;
}
/**
* Check if a path contains a domain object with a given key string
* @param {string} keyStringToCheck the keystring to check for
* @param {Array<DomainObject>} path the path to check
* @returns {boolean} true if the path contains a DomainObject with the given keystring, otherwise false
*/
#pathContainsDomainObject(keyStringToCheck, path) {
if (!keyStringToCheck) {
return false;
@@ -810,10 +739,10 @@ export default class ObjectAPI {
/**
* Given an identifier, constructs the original path by walking up its parents
* @param {module:openmct.ObjectAPI~Identifier} identifier
* @param {Array<module:openmct.DomainObject>} path an array of path objects
* @param {Identifier} identifier
* @param {Array<DomainObject>} path an array of path objects
* @param {AbortSignal} abortSignal (optional) signal to abort fetch requests
* @returns {Promise<Array<module:openmct.DomainObject>>} a promise containing an array of domain objects
* @returns {Promise<Array<DomainObject>>} a promise containing an array of domain objects
*/
async getOriginalPath(identifier, path = [], abortSignal = null) {
const domainObject = await this.get(identifier, abortSignal);
@@ -873,6 +802,12 @@ export default class ObjectAPI {
return objectPath;
}
/**
* Check if the object is a link based on its path
* @param {DomainObject} domainObject the DomainObject to check
* @param {Array<DomainObject>} objectPath the object path to check
* @returns {boolean} true if the object path is a link, otherwise false
*/
isObjectPathToALink(domainObject, objectPath) {
return (
objectPath !== undefined &&
@@ -881,10 +816,19 @@ export default class ObjectAPI {
);
}
/**
* Check if a transaction is active
* @returns {boolean} true if a transaction is active, otherwise false
*/
isTransactionActive() {
return this.transaction !== undefined && this.transaction !== null;
}
/**
* Check if a domain object has already been persisted
* @param {DomainObject} domainObject the domain object to check
* @returns {boolean} true if the domain object has already been persisted, otherwise false
*/
#hasAlreadyBeenPersisted(domainObject) {
// modified can sometimes be undefined, so make it 0 in this case
const modified = domainObject.modified ?? Number.MIN_SAFE_INTEGER;

View File

@@ -1,4 +1,4 @@
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import mount from 'utils/mount';
import OverlayComponent from './components/OverlayComponent.vue';

View File

@@ -107,7 +107,7 @@ class OverlayAPI {
* displaying messages that require the user's
* immediate attention.
* @param {model} options defines options for the dialog
* @returns {object} with an object with a dismiss function that can be called from the calling code
* @returns {Object} with an object with a dismiss function that can be called from the calling code
* to dismiss/destroy the dialog
*
* A description of the model options that may be passed to the
@@ -134,7 +134,7 @@ class OverlayAPI {
* Displays a blocking (modal) progress dialog. This dialog can be used for
* displaying messages that require the user's attention, and show progress
* @param {model} options defines options for the dialog
* @returns {object} with an object with a dismiss function that can be called from the calling code
* @returns {Object} with an object with a dismiss function that can be called from the calling code
* to dismiss/destroy the dialog and an updateProgress function that takes progressPercentage(Number 0-100)
* and progressText (string)
*

View File

@@ -20,13 +20,29 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
/**
* @typedef {import('openmct').OpenMCT} OpenMCT
* @typedef {import('openmct').Identifier} Identifier
* @typedef {string} Status
*/
import EventEmitter from 'eventemitter3';
/**
* Get, set, and observe statuses for Open MCT objects. A status is a string
* that represents the current state of an object.
*
* @extends EventEmitter
*/
export default class StatusAPI extends EventEmitter {
/**
* Constructs a new instance of the StatusAPI class.
* @param {OpenMCT} openmct - The Open MCT application instance.
*/
constructor(openmct) {
super();
this._openmct = openmct;
/** @type {Record<string, Status>} */
this._statusCache = {};
this.get = this.get.bind(this);
@@ -34,19 +50,33 @@ export default class StatusAPI extends EventEmitter {
this.observe = this.observe.bind(this);
}
/**
* Retrieves the status of the object with the given identifier.
* @param {Identifier} identifier - The identifier of the object.
* @returns {Status | undefined} The status of the object, or undefined if the object's status is not cached.
*/
get(identifier) {
let keyString = this._openmct.objects.makeKeyString(identifier);
return this._statusCache[keyString];
}
set(identifier, value) {
/**
* Sets the status of the object with the given identifier.
* @param {Identifier} identifier - The identifier of the object.
* @param {Status} status - The new status value for the object.
*/
set(identifier, status) {
let keyString = this._openmct.objects.makeKeyString(identifier);
this._statusCache[keyString] = value;
this.emit(keyString, value);
this._statusCache[keyString] = status;
this.emit(keyString, status);
}
/**
* Deletes the status of the object with the given identifier.
* @param {Identifier} identifier - The identifier of the object.
*/
delete(identifier) {
let keyString = this._openmct.objects.makeKeyString(identifier);
@@ -55,6 +85,13 @@ export default class StatusAPI extends EventEmitter {
delete this._statusCache[keyString];
}
/**
* Observes the status of the object with the given identifier, and calls the provided callback
* function whenever the status changes.
* @param {Identifier} identifier - The identifier of the object.
* @param {(value: any) => void} callback - The function to be called whenever the status changes.
* @returns {() => void} A function that can be called to stop observing the status.
*/
observe(identifier, callback) {
let key = this._openmct.objects.makeKeyString(identifier);

View File

@@ -156,7 +156,7 @@ class BatchingWebSocket extends EventTarget {
}
/**
* @param {Number} maxBatchSize the maximum length of a batch of messages. For example,
* @param {number} maxBatchSize the maximum length of a batch of messages. For example,
* the maximum number of telemetry values to batch before dropping them
* Note that this is a fail-safe that is only invoked if performance drops to the
* point where Open MCT cannot keep up with the amount of telemetry it is receiving.

View File

@@ -38,19 +38,19 @@ import TelemetryValueFormatter from './TelemetryValueFormatter.js';
* Describes and bounds requests for telemetry data.
*
* @typedef TelemetryRequestOptions
* @property {String} [sort] the key of the property to sort by. This may
* @property {string} [sort] the key of the property to sort by. This may
* be prefixed with a "+" or a "-" sign to sort in ascending
* or descending order respectively. If no prefix is present,
* ascending order will be used.
* @property {Number} [start] the lower bound for values of the sorting property
* @property {Number} [end] the upper bound for values of the sorting property
* @property {String} [strategy] symbolic identifier for strategies
* @property {number} [start] the lower bound for values of the sorting property
* @property {number} [end] the upper bound for values of the sorting property
* @property {string} [strategy] symbolic identifier for strategies
* (such as `latest` or `minmax`) which may be recognized by providers;
* these will be tried in order until an appropriate provider
* is found
* @property {AbortController} [signal] an AbortController which can be used
* to cancel a telemetry request
* @property {String} [domain] the domain key of the request
* @property {string} [domain] the domain key of the request
* @property {TimeContext} [timeContext] the time context to use for this request
* @memberof module:openmct.TelemetryAPI~
*/
@@ -59,7 +59,7 @@ import TelemetryValueFormatter from './TelemetryValueFormatter.js';
* Describes and bounds requests for telemetry data.
*
* @typedef TelemetrySubscriptionOptions
* @property {String} [strategy] symbolic identifier directing providers on how
* @property {string} [strategy] symbolic identifier directing providers on how
* to handle telemetry subscriptions. The default behavior is 'latest' which will
* always return a single telemetry value with each callback, and in the event
* of throttling will always prioritize the latest data, meaning intermediate
@@ -727,7 +727,7 @@ export default class TelemetryAPI {
* Get a format map of all value formatters for a given piece of telemetry
* metadata.
*
* @returns {Object<String, {TelemetryValueFormatter}>}
* @returns {Record<string, TelemetryValueFormatter>}
*/
getFormatMap(metadata) {
if (!metadata) {
@@ -936,7 +936,7 @@ export default class TelemetryAPI {
*/
/**
* @typedef {object} LimitsResponseObject
* @typedef {Object} LimitsResponseObject
* @memberof {module:openmct.TelemetryAPI~}
* @property {LimitDefinition} limitLevel the level name and it's limit definition
* @example {
@@ -966,7 +966,7 @@ export default class TelemetryAPI {
* @typedef LimitDefinitionValue
* @memberof {module:openmct.TelemetryAPI~}
* @property {string} color color to represent this limit
* @property {Number} value the limit value
* @property {number} value the limit value
*/
/**
@@ -1050,9 +1050,9 @@ export default class TelemetryAPI {
*/
/**
* @typedef {object} StalenessResponseObject
* @property {Boolean} isStale boolean representing the staleness state
* @property {Number} timestamp Unix timestamp in milliseconds
* @typedef {Object} StalenessResponseObject
* @property {boolean} isStale boolean representing the staleness state
* @property {number} timestamp Unix timestamp in milliseconds
*/
/**

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import EventEmitter from 'eventemitter3';
import _ from 'lodash';
import { TIME_CONTEXT_EVENTS } from '../time/constants.js';

View File

@@ -55,7 +55,7 @@ export default function installWorker() {
/**
* Establish a new WebSocket connection to the given URL
* @param {String} url
* @param {string} url
*/
connect(url) {
this.#wsUrl = url;

View File

@@ -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);
}

Some files were not shown because too many files have changed in this diff Show More