diff --git a/.circleci/config.yml b/.circleci/config.yml index cff2d5e525..3dfa1d6569 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -197,7 +197,9 @@ jobs: steps: - build_and_install: node-version: <> - - run: npm run test:perf + - run: npm run test:perf:memory + - run: npm run test:perf:localhost + - run: npm run test:perf:contract - store_test_results: path: test-results/results.xml - store_artifacts: diff --git a/.cspell.json b/.cspell.json index 49dc94e839..d09743f357 100644 --- a/.cspell.json +++ b/.cspell.json @@ -480,7 +480,10 @@ "sinonjs", "generatedata", "grandsearch", - "websockets" + "websockets", + "swgs", + "memlab", + "devmode" ], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"], "ignorePaths": [ diff --git a/.webpack/webpack.common.js b/.webpack/webpack.common.js index 302211deff..ac25fb6af9 100644 --- a/.webpack/webpack.common.js +++ b/.webpack/webpack.common.js @@ -33,6 +33,16 @@ const projectRootDir = path.resolve(__dirname, '..'); /** @type {import('webpack').Configuration} */ const config = { context: projectRootDir, + devServer: { + client: { + progress: true, + overlay: { + // Disable overlay for runtime errors. + // See: https://github.com/webpack/webpack-dev-server/issues/4771 + runtimeErrors: false + } + } + }, entry: { openmct: './openmct.js', generatorWorker: './example/generator/generatorWorker.js', @@ -125,6 +135,7 @@ const config = { loader: 'vue-loader', options: { compilerOptions: { + hoistStatic: false, whitespace: 'preserve', compatConfig: { MODE: 2 diff --git a/.webpack/webpack.dev.js b/.webpack/webpack.dev.js index 4e594148c0..95c5dc5271 100644 --- a/.webpack/webpack.dev.js +++ b/.webpack/webpack.dev.js @@ -45,14 +45,6 @@ module.exports = merge(common, { directory: path.join(__dirname, '..', '/dist'), publicPath: '/dist', watch: false - }, - client: { - progress: true, - overlay: { - // Disable overlay for runtime errors. - // See: https://github.com/webpack/webpack-dev-server/issues/4771 - runtimeErrors: false - } } } }); diff --git a/e2e/README.md b/e2e/README.md index 8b1c9db91b..f83c36391f 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -134,11 +134,11 @@ npm run test:e2e:updatesnapshots ## Performance Testing -The open source performance tests function mostly as a contract for the locator logic, functionality, and assumptions will work in our downstream, closed source test suites. +The open source performance tests function in three ways which match their naming and folder structure: -They're found under `./e2e/tests/performance` and are to be executed with the following npm script: - -`npm run test:perf` +`./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. These tests are expected to become blocking and gating with assertions as we extend the capabilities of Playwright. @@ -158,8 +158,11 @@ Our file structure follows the type of type of testing being excercised at the e |`./tests/functional/example/` | Tests which specifically verify the example plugins (e.g.: Sine Wave Generator).| |`./tests/functional/plugins/` | Tests which loosely test each plugin. This folder is the most likely to change. Note: some `@snapshot` tests are still contained within this structure.| |`./tests/framework/` | Tests which verify that our testing framework's functionality and assumptions will continue to work based on further refactoring or Playwright version changes (e.g.: verifying custom fixtures and appActions).| -|`./tests/performance/` | Performance tests.| +|`./tests/performance/` | Performance tests which should be run on every commit.| +|`./tests/performance/contract/` | A subset of performance tests which are designed to provide a contract between the open source tests which are run on every commit and the downstream tests which are run post merge and with other frameworks.| +|`./tests/performance/memory` | A subset of performance tests which are designed to test for memory leaks.| |`./tests/visual/` | Visual tests.| +|`./tests/visual/component/` | Visual tests which are only run against a single component.| |`./appActions.js` | Contains common methods which can be leveraged by test case authors to quickly move through the application when writing new tests.| |`./baseFixture.js` | Contains base fixtures which only extend default `@playwright/test` functionality. The expectation is that these fixtures will be removed as the native Playwright API improves| @@ -176,6 +179,7 @@ Open MCT is leveraging the [config file](https://playwright.dev/docs/test-config |`./playwright-ci.config.js` | Used when running in CI or to debug CI issues locally| |`./playwright-local.config.js` | Used when running locally| |`./playwright-performance.config.js` | Used when running performance tests in CI or locally| +|`./playwright-performance-devmode.config.js` | Used when running performance tests in CI or locally| |`./playwright-visual.config.js` | Used to run the visual tests in CI or locally| #### Test Tags diff --git a/e2e/playwright-performance.config.js b/e2e/playwright-performance-dev.config.js similarity index 67% rename from e2e/playwright-performance.config.js rename to e2e/playwright-performance-dev.config.js index 1e38be3df2..f437b11a97 100644 --- a/e2e/playwright-performance.config.js +++ b/e2e/playwright-performance-dev.config.js @@ -2,25 +2,24 @@ // playwright.config.js // @ts-check -const CI = process.env.CI === 'true'; - /** @type {import('@playwright/test').PlaywrightTestConfig} */ const config = { retries: 1, //Only for debugging purposes for trace: 'on-first-retry' testDir: 'tests/performance/', + testMatch: '*.contract.perf.spec.js', //Run everything except contract tests which require marks in dev mode timeout: 60 * 1000, workers: 1, //Only run in serial with 1 worker webServer: { - command: 'npm run start', //coverage not generated + command: 'npm run start', //need development mode for performance.marks and others url: 'http://localhost:8080/#', timeout: 200 * 1000, - reuseExistingServer: !CI + reuseExistingServer: false }, use: { browserName: 'chromium', baseURL: 'http://localhost:8080/', - headless: CI, //Only if running locally - ignoreHTTPSErrors: true, + headless: true, + ignoreHTTPSErrors: false, //HTTP performance varies! screenshot: 'off', trace: 'on-first-retry', video: 'off' @@ -28,6 +27,7 @@ const config = { projects: [ { name: 'chrome', + testIgnore: '*.memory.perf.spec.js', //Do not run memory tests without proper flags. Shouldn't get here use: { browserName: 'chromium' } diff --git a/e2e/playwright-performance-prod.config.js b/e2e/playwright-performance-prod.config.js new file mode 100644 index 0000000000..c9326356d9 --- /dev/null +++ b/e2e/playwright-performance-prod.config.js @@ -0,0 +1,60 @@ +/* eslint-disable no-undef */ +// playwright.config.js +// @ts-check + +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +const config = { + retries: 0, //Only for debugging purposes for trace: 'on-first-retry' + testDir: 'tests/performance/', + testIgnore: '*.contract.perf.spec.js', //Run everything except contract tests which require marks in dev mode + timeout: 60 * 1000, + workers: 1, //Only run in serial with 1 worker + webServer: { + command: 'npm run start:prod', //Production mode + url: 'http://localhost:8080/#', + timeout: 200 * 1000, + reuseExistingServer: false //Must be run with this option to prevent dev mode + }, + use: { + baseURL: 'http://localhost:8080/', + headless: true, + ignoreHTTPSErrors: false, //HTTP performance varies! + screenshot: 'off', + trace: 'on-first-retry', + video: 'off' + }, + projects: [ + { + name: 'chrome-memory', + testMatch: '*.memory.perf.spec.js', //Only run memory tests + use: { + browserName: 'chromium', + launchOptions: { + args: [ + '--no-sandbox', + '--disable-notifications', + '--use-fake-ui-for-media-stream', + '--use-fake-device-for-media-stream', + '--js-flags=--no-move-object-start --expose-gc', + '--enable-precise-memory-info', + '--display=:100' + ] + } + } + }, + { + name: 'chrome', + testIgnore: '*.memory.perf.spec.js', //Do not run memory tests without proper flags + use: { + browserName: 'chromium' + } + } + ], + reporter: [ + ['list'], + ['junit', { outputFile: '../test-results/results.xml' }], + ['json', { outputFile: '../test-results/results.json' }] + ] +}; + +module.exports = config; diff --git a/e2e/test-data/memory-leak-detection.json b/e2e/test-data/memory-leak-detection.json new file mode 100644 index 0000000000..8f931816b8 --- /dev/null +++ b/e2e/test-data/memory-leak-detection.json @@ -0,0 +1 @@ +{"openmct":{"40c410d9-ffd3-4f86-80b6-254155abad47":{"identifier":{"key":"40c410d9-ffd3-4f86-80b6-254155abad47","namespace":""},"name":"Memory Leak detection","type":"folder","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""},{"key":"215fc98f-7e43-4541-a39d-05b5d6e66568","namespace":""},{"key":"d37fdd1f-3371-46cb-9ce8-da0d09d6b2a6","namespace":""},{"key":"846a70a0-479e-4fba-af6e-cd2b7bd2495f","namespace":""},{"key":"3562385a-95b5-4658-9dda-721027d54392","namespace":""},{"key":"d803b99c-dbbd-423a-9162-edb551b14944","namespace":""},{"key":"bb3b5ba2-e5b8-440e-8556-32142f143940","namespace":""},{"key":"5a7d5cd9-187a-4552-b304-4fcce98c0581","namespace":""},{"key":"5a7c42c3-4097-4e5c-89c4-f3cffd1f6872","namespace":""},{"key":"853c42cc-b812-481e-9dd3-3206d57c25ff","namespace":""}],"modified":1686180997459,"location":"mine","created":1686172538912,"persisted":1686180997460},"1a19731b-7611-4213-ab5b-e91d2aa64244":{"identifier":{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""},"name":"1hz-swg","type":"generator","telemetry":{"period":10,"amplitude":1,"offset":0,"dataRateInHz":1,"phase":0,"randomness":0,"loadDelay":0,"infinityValues":false,"staleness":false},"modified":1686175185687,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686172550026,"persisted":1686175185687},"215fc98f-7e43-4541-a39d-05b5d6e66568":{"identifier":{"key":"215fc98f-7e43-4541-a39d-05b5d6e66568","namespace":""},"name":"overlay-plot-single-1hz-swg","type":"telemetry.plot.overlay","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}],"configuration":{"series":[{"identifier":{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}}]},"modified":1686175116632,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686175111102,"persisted":1686175116633},"d37fdd1f-3371-46cb-9ce8-da0d09d6b2a6":{"identifier":{"key":"d37fdd1f-3371-46cb-9ce8-da0d09d6b2a6","namespace":""},"name":"stacked-plot-single-1hz-swg","type":"telemetry.plot.stacked","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}],"configuration":{"series":[],"yAxis":{},"xAxis":{}},"modified":1686175150618,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686175142052,"persisted":1686175150618},"846a70a0-479e-4fba-af6e-cd2b7bd2495f":{"identifier":{"key":"846a70a0-479e-4fba-af6e-cd2b7bd2495f","namespace":""},"name":"lad-table-single-1hz-swg","type":"LadTable","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}],"modified":1686175174753,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686175170737,"persisted":1686175174753},"3562385a-95b5-4658-9dda-721027d54392":{"identifier":{"key":"3562385a-95b5-4658-9dda-721027d54392","namespace":""},"name":"telemetry-table-single-1hz-swg","type":"table","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}],"configuration":{"columnWidths":{},"hiddenColumns":{},"columnOrder":[],"cellFormat":{},"autosize":true},"modified":1686175210572,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686175204072,"persisted":1686175210572},"d803b99c-dbbd-423a-9162-edb551b14944":{"identifier":{"key":"d803b99c-dbbd-423a-9162-edb551b14944","namespace":""},"name":"lad-table-set-single-1hz-swg","type":"LadTableSet","composition":[{"key":"846a70a0-479e-4fba-af6e-cd2b7bd2495f","namespace":""}],"modified":1686176874262,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686176867116,"persisted":1686176874262},"bb3b5ba2-e5b8-440e-8556-32142f143940":{"identifier":{"key":"bb3b5ba2-e5b8-440e-8556-32142f143940","namespace":""},"name":"notebook-memory-leak-detection-test","type":"notebook","configuration":{"defaultSort":"oldest","entries":{"551e1ce9-0263-416d-90b0-41beebc9d50d":{"f536da89-c2e8-4b7f-b25d-5ee7c8ac8df3":[{"id":"entry-5e9bda4c-93cd-4fe7-9c0a-5ce419187fa9","createdOn":1686176929016,"createdBy":null,"text":"First entry","embeds":[],"modifiedBy":"Unknown","modified":1686176934867},{"id":"entry-dc466597-2584-4ecd-9a78-038a60c6a2dd","createdOn":1686176935883,"createdBy":null,"text":"Second entry","embeds":[],"modifiedBy":"Unknown","modified":1686176942618}]},"f298ac79-ed27-467e-ba9d-042094c8f8fa":{"156aabf8-db82-4b5a-b362-883d46b738cb":[{"id":"entry-70b81129-f42a-4747-955e-0c803f360deb","createdOn":1686176971570,"createdBy":null,"text":"First entry of First Page of Second Section","embeds":[],"modifiedBy":"Unknown","modified":1686176983771},{"id":"entry-b0dc1556-c5ab-4b04-85c1-ef0d4a744306","createdOn":1686176992402,"createdBy":null,"text":"Second entry of first page of second section with embedded object","embeds":[{"bounds":{"start":1670516888271,"end":1670518688271},"createdOn":1686176992389,"createdBy":null,"cssClass":"icon-plot-stacked","domainObject":{"identifier":{"key":"d37fdd1f-3371-46cb-9ce8-da0d09d6b2a6","namespace":""},"name":"stacked-plot-single-1hz-swg","type":"telemetry.plot.stacked","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}],"configuration":{"series":[],"yAxis":{},"xAxis":{}},"modified":1686175150618,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686175142052,"persisted":1686175150618},"historicLink":"#/browse/mine/40c410d9-ffd3-4f86-80b6-254155abad47/d37fdd1f-3371-46cb-9ce8-da0d09d6b2a6?tc.mode=fixed&tc.startBound=1670516888271&tc.endBound=1670518688271&tc.timeSystem=utc&view=plot-stacked","id":"embed-1686176992389","name":"stacked-plot-single-1hz-swg","snapshot":{"fullSizeImageObjectIdentifier":{"key":"ea82c76b-c720-4935-a26d-474be113538f","namespace":""},"thumbnailImage":{"src":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAAAi1JREFUSEvt171vUlEYx/HvLS0UpIBBLViNotG46KaTJo0Dswt/QNOZMPMPMGNnwuhA0j+AODgZ20VNjE3aBBJfImCQl1reWugxj08hRkthKQx4xhvO+Zznd57LvdcyxhimMKz/8KRSt7rdrrHZbJPyBo5VKBRMIBCYPDx7zZXP500oFBpELbe1ZVnnHr2VyWRMJBI5d+hvYHp/IIlEwsTj8RmqePZup6lVnEwmTSwWm3xzTQ2eWtSpVMqsr69PPupwOGyy2ezk4eebr8zT1dXf8NEx5GrwvQ07FcjXQd4E5fqfY84CeYz4F+GWB6664Y4Pgi6QV0d5xrS6Ou9bA94U4UcbKi1oyVrym42NDRONRv+pWCYWGvChAtUObOag04PwdXDY4NGyoped44UlsM2CF3tQaMLQh8Rp8LIL7vkUvuaGx0GFe0YXPWsI/GkftkpDYIlW1hBY4v7a0Irfl+HgCJ4EFbrogIdXdAPjwHs1ODhU+F35lIr7sCC7VYVrHdip6jkJXGrqmQrsnAefY3TFffhtGbZLQ+B2V6sQWHboXlB45QJ47Qq7FuBZSOElO9jnzo5aYNn4x8oJvLa2ZtLp9GDWsdEmEnirCLs1heVauwd3fZA76XZpsNue8WE5rv3DIRX3YdnJ64LCUqVspH6o8HYR/E7tbO8ChLyjK5bkPv9UuHl0StQCSzO55hV++QXu+xW+uQStnsIPLsGKW+EbHlgc8U3Qh6Vp6x34BWH+QY6XeEwMAAAAAElFTkSuQmCC"}},"type":"d37fdd1f-3371-46cb-9ce8-da0d09d6b2a6"}],"modifiedBy":"Unknown","modified":1686177011706}]}},"imageMigrationVer":"v1","pageTitle":"Page","sections":[{"id":"551e1ce9-0263-416d-90b0-41beebc9d50d","isDefault":false,"isSelected":false,"name":"First Section","pages":[{"id":"f536da89-c2e8-4b7f-b25d-5ee7c8ac8df3","isDefault":false,"isSelected":false,"name":"First Page","pageTitle":"Page"},{"id":"96a12ca2-7903-4417-a7cd-1e6bd3140af2","isDefault":false,"isSelected":true,"name":"Second Page","pageTitle":"Page"}],"sectionTitle":"Section"},{"id":"f298ac79-ed27-467e-ba9d-042094c8f8fa","isDefault":false,"isSelected":true,"name":"Second Section","pages":[{"id":"156aabf8-db82-4b5a-b362-883d46b738cb","isDefault":false,"isSelected":true,"name":"First Page Page","pageTitle":"Page"}],"sectionTitle":"Section"}],"sectionTitle":"Section","type":"General"},"modified":1686177055709,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686176926987,"persisted":1686177055709},"5a7d5cd9-187a-4552-b304-4fcce98c0581":{"identifier":{"key":"5a7d5cd9-187a-4552-b304-4fcce98c0581","namespace":""},"name":"tabbed-display-memory-leak-test","type":"folder","composition":["c233f060-849a-44f3-9cdc-8377f75efa13","75832ab9-8274-489e-8349-89e53162cbcf"],"location":"40c410d9-ffd3-4f86-80b6-254155abad47","modified":1686177652421,"created":1686177538370,"persisted":1686177652421},"5a7c42c3-4097-4e5c-89c4-f3cffd1f6872":{"identifier":{"key":"5a7c42c3-4097-4e5c-89c4-f3cffd1f6872","namespace":""},"name":"display-layout-single-1hz-swg","type":"layout","composition":[{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""}],"configuration":{"items":[{"identifier":{"key":"1a19731b-7611-4213-ab5b-e91d2aa64244","namespace":""},"x":19,"y":6,"width":20,"height":5,"displayMode":"all","value":"sin","stroke":"","fill":"","color":"","fontSize":"default","font":"default","type":"telemetry-view","id":"a20b9455-504a-4e66-bcb0-9b3ec95cfe2f"}],"layoutGrid":[10,10]},"modified":1686180670328,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686180656767,"persisted":1686180670329},"853c42cc-b812-481e-9dd3-3206d57c25ff":{"identifier":{"key":"853c42cc-b812-481e-9dd3-3206d57c25ff","namespace":""},"name":"display-layout-single-overlay-plot","type":"layout","composition":[{"key":"215fc98f-7e43-4541-a39d-05b5d6e66568","namespace":""}],"configuration":{"items":[{"width":56,"height":29,"x":23,"y":13,"identifier":{"key":"215fc98f-7e43-4541-a39d-05b5d6e66568","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"b36dd532-166f-418a-9bb5-0f5962a8db51"}],"layoutGrid":[10,10]},"modified":1686181004610,"location":"40c410d9-ffd3-4f86-80b6-254155abad47","created":1686180997447,"persisted":1686181004611},"c233f060-849a-44f3-9cdc-8377f75efa13":{"identifier":{"namespace":"","key":"c233f060-849a-44f3-9cdc-8377f75efa13"},"composition":["fcc452e0-4fce-4791-94c5-d31299def333","4174ecc6-0af1-4e46-bc3a-fe4d8703561e","71095940-7e82-4afb-ad4e-e3b2626a532b","e7d2df8a-58d7-49c4-adb0-db03a678043c","8b7c308c-b71a-4189-a414-c9a4eab4b62c","3cc398f7-b45b-4e29-9d8a-792e866c3c41","1f41b32a-2bd9-4e97-9537-b9fa9c734fec","bb5553c7-6ca3-4599-997b-ae527e5166ab"],"name":"Mock Spacecraft Telemetry","type":"folder","location":"5a7d5cd9-187a-4552-b304-4fcce98c0581","modified":1686177538372,"created":1686177538372,"persisted":1686177538372},"75832ab9-8274-489e-8349-89e53162cbcf":{"identifier":{"namespace":"","key":"75832ab9-8274-489e-8349-89e53162cbcf"},"type":"folder","location":"5a7d5cd9-187a-4552-b304-4fcce98c0581","composition":["354d50f1-741e-41c4-b19c-3c754f6981fc","2dee2f04-4b6a-495f-94c3-ea9545babd87","7112d03a-b858-45ed-9dd4-e854f06b2139",{"key":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","namespace":""},{"key":"e53004f2-76f5-432f-af6c-74dff213324b","namespace":""},{"key":"c6f1c75f-46c2-4ffc-97c4-428aad405066","namespace":""},{"key":"8a334017-8419-4fe5-a797-2bd8ef4c4a2a","namespace":""},{"key":"ef3659cc-2a17-4bcb-8a4b-b6f15d97f8f1","namespace":""},{"key":"5077be4e-d940-46c5-99d4-c8e55a565933","namespace":""}],"name":"Components","modified":1686177882467,"conditionalLabel":"","created":1686177538397,"persisted":1686177882467},"fcc452e0-4fce-4791-94c5-d31299def333":{"identifier":{"namespace":"","key":"fcc452e0-4fce-4791-94c5-d31299def333"},"telemetry":{"period":10000000,"amplitude":"10","offset":0,"dataRateInHz":1,"phase":0,"randomness":0.001},"name":"Navcam Pan","type":"generator","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538442,"id":"fcc452e0-4fce-4791-94c5-d31299def333","created":1686177538375,"persisted":1686177538442},"4174ecc6-0af1-4e46-bc3a-fe4d8703561e":{"identifier":{"namespace":"","key":"4174ecc6-0af1-4e46-bc3a-fe4d8703561e"},"telemetry":{"period":10000000,"amplitude":"10","offset":"0","dataRateInHz":1,"phase":3.14,"randomness":"0.1"},"name":"Joint Velocity","type":"generator","id":"4174ecc6-0af1-4e46-bc3a-fe4d8703561e","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538509,"created":1686177538378,"persisted":1686177538509},"71095940-7e82-4afb-ad4e-e3b2626a532b":{"identifier":{"namespace":"","key":"71095940-7e82-4afb-ad4e-e3b2626a532b"},"telemetry":{"period":10000000,"amplitude":10,"offset":20,"dataRateInHz":1,"phase":1,"randomness":0.001},"name":"Navcam Tilt","type":"generator","id":"71095940-7e82-4afb-ad4e-e3b2626a532b","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538450,"created":1686177538380,"persisted":1686177538450},"e7d2df8a-58d7-49c4-adb0-db03a678043c":{"identifier":{"namespace":"","key":"e7d2df8a-58d7-49c4-adb0-db03a678043c"},"telemetry":{"period":10000000,"amplitude":20,"offset":0,"dataRateInHz":1,"phase":3.14,"randomness":0.001},"name":"Joint Position","type":"generator","id":"e7d2df8a-58d7-49c4-adb0-db03a678043c","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538464,"created":1686177538383,"persisted":1686177538464},"8b7c308c-b71a-4189-a414-c9a4eab4b62c":{"identifier":{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"},"telemetry":{"period":10000000,"amplitude":100,"offset":100,"dataRateInHz":1,"phase":0,"randomness":0.001},"name":"Battery SOC","type":"generator","id":"8b7c308c-b71a-4189-a414-c9a4eab4b62c","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538475,"created":1686177538386,"persisted":1686177538475},"3cc398f7-b45b-4e29-9d8a-792e866c3c41":{"identifier":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"telemetry":{"period":"1000","amplitude":"25","offset":0,"dataRateInHz":1,"phase":0.5,"randomness":0},"name":"Rover Roll","type":"generator","id":"3cc398f7-b45b-4e29-9d8a-792e866c3c41","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538490,"created":1686177538388,"persisted":1686177538490},"1f41b32a-2bd9-4e97-9537-b9fa9c734fec":{"identifier":{"namespace":"","key":"1f41b32a-2bd9-4e97-9537-b9fa9c734fec"},"telemetry":{"period":10000000,"amplitude":45,"offset":0,"dataRateInHz":1,"phase":1,"randomness":0.001},"name":"Rover Pitch","type":"generator","id":"1f41b32a-2bd9-4e97-9537-b9fa9c734fec","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538435,"created":1686177538391,"persisted":1686177538435},"bb5553c7-6ca3-4599-997b-ae527e5166ab":{"identifier":{"namespace":"","key":"bb5553c7-6ca3-4599-997b-ae527e5166ab"},"telemetry":{"period":10000000,"amplitude":45,"offset":0,"dataRateInHz":1,"phase":0,"randomness":0.001},"name":"Rover Yaw","type":"generator","id":"bb5553c7-6ca3-4599-997b-ae527e5166ab","location":"c233f060-849a-44f3-9cdc-8377f75efa13","modified":1686177538422,"created":1686177538394,"persisted":1686177538422},"354d50f1-741e-41c4-b19c-3c754f6981fc":{"identifier":{"namespace":"","key":"354d50f1-741e-41c4-b19c-3c754f6981fc"},"composition":["2dee2f04-4b6a-495f-94c3-ea9545babd87",{"key":"c6f1c75f-46c2-4ffc-97c4-428aad405066","namespace":""},{"key":"8a334017-8419-4fe5-a797-2bd8ef4c4a2a","namespace":""}],"keep_alive":true,"name":"tab-view-simple-memory-leak-test","type":"tabs","location":"75832ab9-8274-489e-8349-89e53162cbcf","modified":1686178032189,"configuration":{"objectStyles":{}},"conditionalLabel":"","created":1686177538400,"persisted":1686178032189,"currentTabIndex":0},"2dee2f04-4b6a-495f-94c3-ea9545babd87":{"identifier":{"namespace":"","key":"2dee2f04-4b6a-495f-94c3-ea9545babd87"},"composition":["fcc452e0-4fce-4791-94c5-d31299def333","4174ecc6-0af1-4e46-bc3a-fe4d8703561e","71095940-7e82-4afb-ad4e-e3b2626a532b","e7d2df8a-58d7-49c4-adb0-db03a678043c","16604b6b-1db8-46e7-bdb3-da1aabda5947","d84af5a0-262c-4738-8ba0-afc5423a3dee","c5374d8e-0fe0-4c53-814b-40f3f8b7a78c","018cb047-2143-4a12-97cd-bb8428466155","76f57661-2ab1-40bd-9591-1d39fd550f84","1863a7b7-2324-4851-9cb0-5af9c590e3d3","70ba355c-355b-4194-938e-914c5ad3f890",{"key":"2496ca66-2b5c-4d1e-a694-a0f58baab374","namespace":""},{"key":"a8d6235f-34af-4602-9131-7955f85bacaa","namespace":""},{"key":"3e20e749-b537-477d-b1ca-d12ada3f2d84","namespace":""},{"key":"de51f3a6-5076-472c-bafb-5824575691e3","namespace":""}],"configuration":{"items":[{"stroke":"transparent","x":44,"y":10,"width":28,"height":21,"url":"https://i.imgur.com/i1zz1we.png","type":"image-view","id":"3c59df18-90d2-4fe3-b617-1626af59b7ca"},{"x":54,"y":29,"x2":54,"y2":24,"stroke":"#666666","type":"line-view","id":"6a23785c-11da-4d8b-bea5-314d48c21ce8"},{"identifier":{"namespace":"","key":"fcc452e0-4fce-4791-94c5-d31299def333"},"x":74,"y":13,"width":19,"height":3,"displayMode":"all","value":"sin","stroke":"","fill":"","color":"","fontSize":"default","font":"default","type":"telemetry-view","id":"fc4ce448-e1be-4d92-9f59-df23405793b4"},{"identifier":{"namespace":"","key":"4174ecc6-0af1-4e46-bc3a-fe4d8703561e"},"x":74,"y":19,"width":19,"height":3,"displayMode":"all","value":"sin","stroke":"","fill":"","color":"","fontSize":"default","font":"default","type":"telemetry-view","id":"ac48013e-033f-4a8c-b671-5b2e4aaadd81"},{"identifier":{"namespace":"","key":"71095940-7e82-4afb-ad4e-e3b2626a532b"},"x":74,"y":9,"width":19,"height":3,"displayMode":"all","value":"sin","stroke":"","fill":"","color":"","fontSize":"default","font":"default","type":"telemetry-view","id":"31789462-91b1-407a-9a76-1a83116b4f36","showUnits":false},{"identifier":{"namespace":"","key":"e7d2df8a-58d7-49c4-adb0-db03a678043c"},"x":74,"y":23,"width":19,"height":3,"displayMode":"all","value":"sin","stroke":"","fill":"","color":"","fontSize":"default","font":"default","type":"telemetry-view","id":"155b2358-cb82-4baf-ba0d-d7c92f8f44ce"},{"x":70,"y":29,"x2":54,"y2":29,"stroke":"#666666","type":"line-view","id":"a4146b13-056c-4849-9986-c69f24fb81d8"},{"x":74,"y":10,"x2":71,"y2":10,"stroke":"#666666","type":"line-view","id":"63d6b2e5-653c-4a69-b72f-2e0524dcf428"},{"x":67,"y":17,"x2":73,"y2":17,"stroke":"#666666","type":"line-view","id":"828ae816-54a7-4c7f-9ffe-2491f1a80347"},{"width":47,"height":24,"x":0,"y":6,"identifier":{"key":"16604b6b-1db8-46e7-bdb3-da1aabda5947","namespace":""},"hasFrame":false,"fontSize":"default","font":"default","type":"subobject-view","id":"5e6da1c6-639d-4118-99a4-48d8bfa7a15a"},{"width":47,"height":23,"x":0,"y":53,"identifier":{"key":"d84af5a0-262c-4738-8ba0-afc5423a3dee","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"e538408d-2e87-40df-8c0f-ea852781e67a"},{"width":47,"height":23,"x":0,"y":30,"identifier":{"key":"c5374d8e-0fe0-4c53-814b-40f3f8b7a78c","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"56699891-0144-44e6-a5fd-a0bf225109e8"},{"x":74,"y":21,"x2":68,"y2":21,"stroke":"#666666","type":"line-view","id":"e05a1873-3c5d-4e68-8b62-1810230879bc"},{"x":68,"y":27,"x2":68,"y2":21,"stroke":"#666666","type":"line-view","id":"c0e15084-350f-429b-8459-645d0fa337e9"},{"x":71,"y":10,"x2":71,"y2":15,"stroke":"#666666","type":"line-view","id":"af41777d-3027-4626-baa6-2a09cd3983e0"},{"x":67,"y":15,"x2":71,"y2":15,"stroke":"#666666","type":"line-view","id":"5a26d7a7-2905-43b0-bce2-b0b2b942c7bc"},{"x":68,"y":27,"x2":56,"y2":27,"stroke":"#666666","type":"line-view","id":"a0c9206d-be9d-4c73-aeeb-fb48f530b0fe"},{"x":56,"y":27,"x2":56,"y2":24,"stroke":"#666666","type":"line-view","id":"5e264631-1753-4960-9a94-1ee39774f04c"},{"x":73,"y":14,"x2":73,"y2":17,"stroke":"#666666","type":"line-view","id":"c2b0b637-f29c-475d-8ec0-d82637e52a15"},{"x":74,"y":14,"x2":73,"y2":14,"stroke":"#666666","type":"line-view","id":"5eba5795-58b1-4bb5-9dc3-a8dd9427fe98"},{"x":70,"y":29,"x2":70,"y2":24,"stroke":"#666666","type":"line-view","id":"44e1d2dc-3bd4-4cfd-a78b-0df7557742ef"},{"x":74,"y":24,"x2":70,"y2":24,"stroke":"#666666","type":"line-view","id":"3696d4f1-1534-4494-848a-976fc85f478d"},{"width":46,"height":23,"x":47,"y":53,"identifier":{"key":"018cb047-2143-4a12-97cd-bb8428466155","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"9a744b51-41cc-4fd6-bd14-94846a0a9ca3"},{"width":47,"height":26,"x":0,"y":76,"identifier":{"key":"76f57661-2ab1-40bd-9591-1d39fd550f84","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"3cc086b3-c035-4fba-b71a-8f5e0af4f732"},{"width":46,"height":23,"x":47,"y":30,"identifier":{"key":"1863a7b7-2324-4851-9cb0-5af9c590e3d3","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"2b9dba11-5314-49b0-81fd-3758e467b1b0"},{"width":46,"height":26,"x":47,"y":76,"identifier":{"key":"70ba355c-355b-4194-938e-914c5ad3f890","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"172f6909-3efc-42a2-8568-933974d013a6"},{"width":23,"height":5,"x":0,"y":0,"identifier":{"key":"2496ca66-2b5c-4d1e-a694-a0f58baab374","namespace":""},"hasFrame":false,"fontSize":"default","font":"default","type":"subobject-view","id":"9f725dd0-44b8-43db-818e-1dce4cb1a376"},{"width":23,"height":5,"x":24,"y":0,"identifier":{"key":"a8d6235f-34af-4602-9131-7955f85bacaa","namespace":""},"hasFrame":false,"fontSize":"default","font":"default","type":"subobject-view","id":"1dbfd63a-aa8b-4aae-9daa-1de1de8c1bcc"},{"width":23,"height":5,"x":48,"y":0,"identifier":{"key":"3e20e749-b537-477d-b1ca-d12ada3f2d84","namespace":""},"hasFrame":false,"fontSize":"default","font":"default","type":"subobject-view","id":"74cb215d-851d-440a-8d94-2464e5f86547"},{"width":22,"height":5,"x":72,"y":0,"identifier":{"key":"de51f3a6-5076-472c-bafb-5824575691e3","namespace":""},"hasFrame":false,"fontSize":"default","font":"default","type":"subobject-view","id":"1a894143-a891-4430-9b78-5ece24886eab"}],"layoutGrid":[10,10],"layoutDimensions":{},"objectStyles":{"a4146b13-056c-4849-9986-c69f24fb81d8":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"6a23785c-11da-4d8b-bea5-314d48c21ce8":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"63d6b2e5-653c-4a69-b72f-2e0524dcf428":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"828ae816-54a7-4c7f-9ffe-2491f1a80347":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"e05a1873-3c5d-4e68-8b62-1810230879bc":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"c0e15084-350f-429b-8459-645d0fa337e9":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"af41777d-3027-4626-baa6-2a09cd3983e0":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"5a26d7a7-2905-43b0-bce2-b0b2b942c7bc":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"a0c9206d-be9d-4c73-aeeb-fb48f530b0fe":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"5e264631-1753-4960-9a94-1ee39774f04c":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"c2b0b637-f29c-475d-8ec0-d82637e52a15":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"5eba5795-58b1-4bb5-9dc3-a8dd9427fe98":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"44e1d2dc-3bd4-4cfd-a78b-0df7557742ef":{"staticStyle":{"style":{"border":"1px solid #45818e"}}},"3696d4f1-1534-4494-848a-976fc85f478d":{"staticStyle":{"style":{"border":"1px solid #45818e"}}}}},"name":"display-layout-simple-telemetry","type":"layout","location":"75832ab9-8274-489e-8349-89e53162cbcf","modified":1686178045684,"id":"2dee2f04-4b6a-495f-94c3-ea9545babd87","conditionalLabel":"","created":1686177538403,"persisted":1686178045685},"7112d03a-b858-45ed-9dd4-e854f06b2139":{"identifier":{"namespace":"","key":"7112d03a-b858-45ed-9dd4-e854f06b2139"},"type":"folder","location":"75832ab9-8274-489e-8349-89e53162cbcf","composition":["6e15a93e-aafb-4c7f-8566-7d815f20c703","b7e79486-4a78-4f49-aaf3-e750bb7a9d42","60ef2584-278f-44f1-8358-f5a259acecd2","ad937768-fa10-4aab-bf05-95f1a0d3d731","ef9132ce-6231-4e5d-a191-5109abd363a1","c0995ce5-87e0-4370-a084-889ae334f243"],"name":"Imagery","modified":1686177538582,"created":1686177538582,"persisted":1686177538582},"7fa4017a-16fd-4fe1-9b7e-45f0408201b3":{"identifier":{"key":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","namespace":""},"name":"Imagery 2","type":"folder","composition":[{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""},{"key":"1ce0346e-11aa-4a55-92f7-9d261ae86766","namespace":""},{"key":"effa0531-982a-4296-8252-43acc68dc973","namespace":""},{"key":"da8a5f5a-23ad-44e7-8360-612a542f4ba8","namespace":""},{"key":"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2","namespace":""}],"modified":1686177538615,"location":"75832ab9-8274-489e-8349-89e53162cbcf","created":1686177538615,"persisted":1686177538615},"e53004f2-76f5-432f-af6c-74dff213324b":{"identifier":{"key":"e53004f2-76f5-432f-af6c-74dff213324b","namespace":""},"name":"flexible-layout-images-memory-leak-test","type":"flexible-layout","configuration":{"containers":[{"id":"ca9e43d8-a5f4-4af8-bfb1-61d0382621e7","frames":[{"id":"61f5dde1-b550-4b15-9191-41ebdfe11697","domainObjectIdentifier":{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},"size":33,"noFrame":false},{"id":"23961564-8a92-4d5c-aecd-833d8d77884e","domainObjectIdentifier":{"key":"1ce0346e-11aa-4a55-92f7-9d261ae86766","namespace":""},"size":34,"noFrame":false},{"id":"674929bd-5217-43f1-b4a0-7c28ecca59ab","domainObjectIdentifier":{"key":"effa0531-982a-4296-8252-43acc68dc973","namespace":""},"size":33,"noFrame":false}],"size":50},{"id":"8d6ecc4e-7f08-4252-8c53-5c6c00c90ff5","frames":[{"id":"ba897a57-ddaa-428a-98dc-2139e8553896","domainObjectIdentifier":{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""},"size":33,"noFrame":false},{"id":"bcbc2248-2853-4183-98a6-2e7e78f3da5d","domainObjectIdentifier":{"key":"da8a5f5a-23ad-44e7-8360-612a542f4ba8","namespace":""},"size":34,"noFrame":false},{"id":"7cb521c7-d167-48fc-814a-857d37a15dd5","domainObjectIdentifier":{"key":"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2","namespace":""},"size":33,"noFrame":false}],"size":50}],"rowsLayout":false},"composition":[{"key":"1ce0346e-11aa-4a55-92f7-9d261ae86766","namespace":""},{"key":"effa0531-982a-4296-8252-43acc68dc973","namespace":""},{"key":"da8a5f5a-23ad-44e7-8360-612a542f4ba8","namespace":""},{"key":"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2","namespace":""},{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""}],"modified":1686177855922,"location":"75832ab9-8274-489e-8349-89e53162cbcf","created":1686177538645,"persisted":1686177855922},"c6f1c75f-46c2-4ffc-97c4-428aad405066":{"identifier":{"key":"c6f1c75f-46c2-4ffc-97c4-428aad405066","namespace":""},"name":"display-layout-images-memory-leak-test","type":"layout","composition":[{"key":"1ce0346e-11aa-4a55-92f7-9d261ae86766","namespace":""},{"key":"effa0531-982a-4296-8252-43acc68dc973","namespace":""},{"key":"da8a5f5a-23ad-44e7-8360-612a542f4ba8","namespace":""},{"key":"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2","namespace":""},{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""}],"configuration":{"items":[{"width":42,"height":28,"x":0,"y":0,"identifier":{"key":"1ce0346e-11aa-4a55-92f7-9d261ae86766","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"36ec392c-241f-4b12-9d52-3a6cad878140"},{"width":43,"height":28,"x":43,"y":0,"identifier":{"key":"effa0531-982a-4296-8252-43acc68dc973","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"63d36f95-8d41-46b7-8621-c34a4799d156"},{"width":42,"height":31,"x":0,"y":29,"identifier":{"key":"da8a5f5a-23ad-44e7-8360-612a542f4ba8","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"d768d5d3-898d-4528-bbe0-1696b94016b5"},{"width":42,"height":31,"x":43,"y":29,"identifier":{"key":"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"1d5acab7-f393-40a9-a27d-963de65f3742"},{"width":42,"height":30,"x":0,"y":61,"identifier":{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"c030a252-75e7-42ca-b341-7a29f44eda3c"},{"width":42,"height":30,"x":43,"y":61,"identifier":{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""},"hasFrame":true,"fontSize":"default","font":"default","type":"subobject-view","id":"b552f249-713a-46b4-8576-116a3b83c35a"}],"layoutGrid":[10,10]},"modified":1686177687906,"location":"75832ab9-8274-489e-8349-89e53162cbcf","created":1686177538532,"persisted":1686177687906},"8a334017-8419-4fe5-a797-2bd8ef4c4a2a":{"identifier":{"key":"8a334017-8419-4fe5-a797-2bd8ef4c4a2a","namespace":""},"name":"time-strip-telemetry-memory-leak-test","type":"time-strip","composition":[{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""},{"namespace":"","key":"018cb047-2143-4a12-97cd-bb8428466155"},{"namespace":"","key":"76f57661-2ab1-40bd-9591-1d39fd550f84"},{"namespace":"","key":"c5374d8e-0fe0-4c53-814b-40f3f8b7a78c"},{"namespace":"","key":"16604b6b-1db8-46e7-bdb3-da1aabda5947"}],"configuration":{"useIndependentTime":false,"timeOptions":{"clockOffsets":{"start":-900000,"end":30000},"fixedOffsets":{"start":1645902914886,"end":1645903844886},"mode":{"key":"local"}}},"modified":1686178198179,"location":"75832ab9-8274-489e-8349-89e53162cbcf","created":1686177538564,"persisted":1686178198179},"ef3659cc-2a17-4bcb-8a4b-b6f15d97f8f1":{"identifier":{"key":"ef3659cc-2a17-4bcb-8a4b-b6f15d97f8f1","namespace":""},"name":"flexible-layout-plots-memory-leak-test","type":"flexible-layout","configuration":{"containers":[{"id":"4ff28bc6-a480-4efa-82f5-4496e3a68455","frames":[{"id":"a75001d9-9bd1-4508-aefc-296ca84fc028","domainObjectIdentifier":{"namespace":"","key":"018cb047-2143-4a12-97cd-bb8428466155"},"size":26,"noFrame":false},{"id":"3b146612-3c23-45c3-ad18-813b6641443a","domainObjectIdentifier":{"namespace":"","key":"c5374d8e-0fe0-4c53-814b-40f3f8b7a78c"},"size":25,"noFrame":false},{"id":"9844318c-da0d-4839-83e2-ff42fd3f5581","domainObjectIdentifier":{"namespace":"","key":"16604b6b-1db8-46e7-bdb3-da1aabda5947"},"size":24,"noFrame":false}],"size":50},{"id":"6548300d-0ae2-44da-a5aa-d7c05685fd46","frames":[{"id":"8757a01b-1fcd-465b-bbe0-9864cbdcabe0","domainObjectIdentifier":{"namespace":"","key":"76f57661-2ab1-40bd-9591-1d39fd550f84"},"size":34,"noFrame":false},{"id":"9e60f0d2-a783-469b-a757-a822eabb2db8","domainObjectIdentifier":{"namespace":"","key":"d84af5a0-262c-4738-8ba0-afc5423a3dee"},"size":33,"noFrame":false},{"id":"1cf2e92c-b991-410a-9ff9-92c5f5a63851","domainObjectIdentifier":{"namespace":"","key":"70ba355c-355b-4194-938e-914c5ad3f890"},"size":33,"noFrame":false}],"size":50}],"rowsLayout":false},"composition":[{"namespace":"","key":"018cb047-2143-4a12-97cd-bb8428466155"},{"namespace":"","key":"76f57661-2ab1-40bd-9591-1d39fd550f84"},{"namespace":"","key":"c5374d8e-0fe0-4c53-814b-40f3f8b7a78c"},{"namespace":"","key":"d84af5a0-262c-4738-8ba0-afc5423a3dee"},{"namespace":"","key":"16604b6b-1db8-46e7-bdb3-da1aabda5947"},{"namespace":"","key":"70ba355c-355b-4194-938e-914c5ad3f890"}],"modified":1686178074664,"location":"75832ab9-8274-489e-8349-89e53162cbcf","created":1686177538677,"persisted":1686178074664},"5077be4e-d940-46c5-99d4-c8e55a565933":{"identifier":{"key":"5077be4e-d940-46c5-99d4-c8e55a565933","namespace":""},"name":"Demo Conditions","type":"folder","composition":[{"key":"57da4a09-4db6-4022-b7f9-e5a5b963600a","namespace":""},{"key":"9f2ed308-18d6-4e86-a25f-26c9196788ce","namespace":""},{"key":"d28608ef-6389-4d9b-98a4-2736f7bc511a","namespace":""},{"key":"3e20e749-b537-477d-b1ca-d12ada3f2d84","namespace":""},{"key":"a8d6235f-34af-4602-9131-7955f85bacaa","namespace":""},{"key":"2496ca66-2b5c-4d1e-a694-a0f58baab374","namespace":""},{"key":"de51f3a6-5076-472c-bafb-5824575691e3","namespace":""},{"key":"8d5068d3-9eca-4609-a1c0-8b2591212af7","namespace":""}],"modified":1686177538691,"location":"75832ab9-8274-489e-8349-89e53162cbcf","created":1686177538691,"persisted":1686177538691},"018cb047-2143-4a12-97cd-bb8428466155":{"identifier":{"namespace":"","key":"018cb047-2143-4a12-97cd-bb8428466155"},"type":"telemetry.plot.overlay","composition":["fcc452e0-4fce-4791-94c5-d31299def333"],"configuration":{"series":[{"identifier":{"namespace":"","key":"fcc452e0-4fce-4791-94c5-d31299def333"},"alarmMarkers":false,"color":"#43b0ff"}],"yAxis":{},"xAxis":{}},"name":"Navcam Pan","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538439,"created":1686177538439,"persisted":1686177538439},"76f57661-2ab1-40bd-9591-1d39fd550f84":{"identifier":{"namespace":"","key":"76f57661-2ab1-40bd-9591-1d39fd550f84"},"type":"telemetry.plot.overlay","composition":["71095940-7e82-4afb-ad4e-e3b2626a532b"],"configuration":{"series":[{"identifier":{"namespace":"","key":"71095940-7e82-4afb-ad4e-e3b2626a532b"},"alarmMarkers":false}],"yAxis":{},"xAxis":{}},"name":"Navcam Tilt","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538446,"created":1686177538446,"persisted":1686177538446},"c5374d8e-0fe0-4c53-814b-40f3f8b7a78c":{"identifier":{"namespace":"","key":"c5374d8e-0fe0-4c53-814b-40f3f8b7a78c"},"type":"telemetry.plot.overlay","composition":["1f41b32a-2bd9-4e97-9537-b9fa9c734fec"],"configuration":{"series":[{"identifier":{"namespace":"","key":"1f41b32a-2bd9-4e97-9537-b9fa9c734fec"},"color":"#43b0ff","alarmMarkers":false}],"yAxis":{},"xAxis":{}},"name":"Rover Pitch","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538432,"created":1686177538432,"persisted":1686177538432},"d84af5a0-262c-4738-8ba0-afc5423a3dee":{"identifier":{"namespace":"","key":"d84af5a0-262c-4738-8ba0-afc5423a3dee"},"type":"telemetry.plot.overlay","composition":["3cc398f7-b45b-4e29-9d8a-792e866c3c41"],"configuration":{"series":[{"identifier":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"color":"#8cc9fd","alarmMarkers":false}],"yAxis":{},"xAxis":{}},"name":"Rover Roll","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538425,"created":1686177538425,"persisted":1686177538425},"16604b6b-1db8-46e7-bdb3-da1aabda5947":{"identifier":{"namespace":"","key":"16604b6b-1db8-46e7-bdb3-da1aabda5947"},"type":"telemetry.plot.overlay","composition":["bb5553c7-6ca3-4599-997b-ae527e5166ab"],"configuration":{"series":[{"identifier":{"namespace":"","key":"bb5553c7-6ca3-4599-997b-ae527e5166ab"},"color":"#43b0ff","alarmMarkers":false,"limitLines":true}],"yAxis":{},"xAxis":{},"useIndependentTime":false},"name":"Rover Yaw","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538419,"created":1686177538419,"persisted":1686177538419},"70ba355c-355b-4194-938e-914c5ad3f890":{"identifier":{"namespace":"","key":"70ba355c-355b-4194-938e-914c5ad3f890"},"type":"telemetry.plot.overlay","composition":["e7d2df8a-58d7-49c4-adb0-db03a678043c"],"configuration":{"series":[{"identifier":{"namespace":"","key":"e7d2df8a-58d7-49c4-adb0-db03a678043c"},"alarmMarkers":false}],"yAxis":{},"xAxis":{}},"name":"Joint Position","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538460,"created":1686177538460,"persisted":1686177538460},"1863a7b7-2324-4851-9cb0-5af9c590e3d3":{"identifier":{"namespace":"","key":"1863a7b7-2324-4851-9cb0-5af9c590e3d3"},"type":"telemetry.plot.overlay","composition":["4174ecc6-0af1-4e46-bc3a-fe4d8703561e"],"configuration":{"series":[{"identifier":{"namespace":"","key":"4174ecc6-0af1-4e46-bc3a-fe4d8703561e"},"color":"#43b0ff","alarmMarkers":false,"limitLines":true}],"yAxis":{"autoscale":false,"range":{"min":5,"max":13}},"xAxis":{}},"name":"Joint Velocity","location":"2dee2f04-4b6a-495f-94c3-ea9545babd87","modified":1686177538453,"created":1686177538453,"persisted":1686177538453},"2496ca66-2b5c-4d1e-a694-a0f58baab374":{"identifier":{"key":"2496ca66-2b5c-4d1e-a694-a0f58baab374","namespace":""},"name":"Battery Widget","type":"conditionWidget","configuration":{"objectStyles":{"styles":[{"conditionId":"735765b4-bf8f-4a54-b31d-3fdf6282503e","style":{"isStyleInvisible":"","backgroundColor":"#274e13","border":"1px solid #00ff00","color":"#00ff00","output":"BATT. GOOD"}},{"conditionId":"d849fc0b-a42d-4551-b456-cd0d6a35f216","style":{"isStyleInvisible":"","backgroundColor":"#b45f06","border":"1px solid #ffff00","color":"#ffff00","output":"BATT. MODERATE"}},{"conditionId":"0d5c4526-37bf-449c-bf21-4aaec93b088c","style":{"isStyleInvisible":"","backgroundColor":"#660000","border":"1px solid #ff0000","color":"#ff0000","output":"BATT. MARGINAL"}},{"conditionId":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","style":{"isStyleInvisible":"","backgroundColor":"","border":"","color":"","output":"BATT. ERROR"}}],"staticStyle":{"style":{"backgroundColor":"","border":"","color":""}},"selectedConditionId":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","defaultConditionId":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","conditionSetIdentifier":{"key":"9f2ed308-18d6-4e86-a25f-26c9196788ce","namespace":""}},"useConditionSetOutputAsLabel":true},"label":"Battery Widget","conditionalLabel":"BATT. MODERATE","modified":1686177538467,"location":"5077be4e-d940-46c5-99d4-c8e55a565933","created":1686177538467,"persisted":1686177538467},"a8d6235f-34af-4602-9131-7955f85bacaa":{"identifier":{"key":"a8d6235f-34af-4602-9131-7955f85bacaa","namespace":""},"name":"Roll Widget","type":"conditionWidget","configuration":{"objectStyles":{"styles":[{"conditionId":"56c960e7-a5a5-43c1-b396-3e21889015ee","style":{"isStyleInvisible":"","backgroundColor":"#660000","border":"1px solid #ff0000","color":"#ff0000","output":"ROLL DANGER"}},{"conditionId":"04ee9247-e704-4c1a-a377-c572f93e9b44","style":{"isStyleInvisible":"","backgroundColor":"#660000","border":"1px solid #ff0000","color":"#ff0000","output":"ROLL DANGER"}},{"conditionId":"6ca762eb-343d-4757-b219-dbbac8a387e6","style":{"isStyleInvisible":"","backgroundColor":"#bf9000","border":"1px solid #ffff00","color":"#ffff00","output":"ROLL WARN."}},{"conditionId":"497ec5d7-75fb-429c-b16c-70dc8200f540","style":{"isStyleInvisible":"","backgroundColor":"#bf9000","border":"1px solid #ffff00","color":"#ffff00","output":"ROLL WARN."}},{"conditionId":"1184d0cc-555d-46ae-a48b-72ee8264934e","style":{"isStyleInvisible":"","backgroundColor":"#38761d","border":"1px solid #00ff00","color":"#00ff00","output":"ROLL OK"}},{"conditionId":"5f345f62-eed3-42d0-8c88-0e654b5c378e","style":{"isStyleInvisible":"","backgroundColor":"","border":"","color":"","output":"ROLL ERR."}}],"staticStyle":{"style":{"backgroundColor":"","border":"","color":""}},"selectedConditionId":"5f345f62-eed3-42d0-8c88-0e654b5c378e","defaultConditionId":"5f345f62-eed3-42d0-8c88-0e654b5c378e","conditionSetIdentifier":{"key":"d28608ef-6389-4d9b-98a4-2736f7bc511a","namespace":""}},"useConditionSetOutputAsLabel":true},"label":"Condition Widget","conditionalLabel":"ROLL DANGER","modified":1686177538478,"location":"5077be4e-d940-46c5-99d4-c8e55a565933","created":1686177538478,"persisted":1686177538478},"3e20e749-b537-477d-b1ca-d12ada3f2d84":{"identifier":{"key":"3e20e749-b537-477d-b1ca-d12ada3f2d84","namespace":""},"name":"Mobility System","type":"conditionWidget","configuration":{"objectStyles":{"styles":[{"conditionId":"be20ffef-b80e-491d-8f4e-fbd14a104926","style":{"isStyleInvisible":"","backgroundColor":"#38761d","border":"1px solid #00ff00","color":"#00ff00","output":"MOBILE"}},{"conditionId":"7cf90ae1-4742-476a-a81b-f6d5bc2feb58","style":{"isStyleInvisible":"","backgroundColor":"#666666","border":"","color":"","output":"STATIONARY"}}],"staticStyle":{"style":{"backgroundColor":"","border":"","color":""}},"selectedConditionId":"7cf90ae1-4742-476a-a81b-f6d5bc2feb58","defaultConditionId":"7cf90ae1-4742-476a-a81b-f6d5bc2feb58","conditionSetIdentifier":{"key":"57da4a09-4db6-4022-b7f9-e5a5b963600a","namespace":""}},"useConditionSetOutputAsLabel":true},"label":"Condition Widget","conditionalLabel":"MOBILE","modified":1686177538496,"location":"5077be4e-d940-46c5-99d4-c8e55a565933","created":1686177538496,"persisted":1686177538496},"de51f3a6-5076-472c-bafb-5824575691e3":{"identifier":{"key":"de51f3a6-5076-472c-bafb-5824575691e3","namespace":""},"name":"Thermal Widget","type":"conditionWidget","configuration":{"objectStyles":{"staticStyle":{"style":{"backgroundColor":"","border":"","color":""}},"styles":[{"conditionId":"735765b4-bf8f-4a54-b31d-3fdf6282503e","style":{"isStyleInvisible":"","backgroundColor":"#274e13","border":"1px solid #00ff00","color":"#00ff00","output":"THERMAL GOOD"}},{"conditionId":"d849fc0b-a42d-4551-b456-cd0d6a35f216","style":{"isStyleInvisible":"","backgroundColor":"#b45f06","border":"1px solid #ffff00","color":"#ffff00","output":"THERMAL WARN."}},{"conditionId":"0d5c4526-37bf-449c-bf21-4aaec93b088c","style":{"isStyleInvisible":"","backgroundColor":"#660000","border":"1px solid #ff0000","color":"#ff0000","output":"THERMAL DANGER"}},{"conditionId":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","style":{"isStyleInvisible":"","backgroundColor":"","border":"","color":"","output":"BATT. ERROR"}}],"selectedConditionId":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","defaultConditionId":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","conditionSetIdentifier":{"key":"8d5068d3-9eca-4609-a1c0-8b2591212af7","namespace":""}},"useConditionSetOutputAsLabel":true},"label":"Battery Widget","conditionalLabel":"THERMAL WARN.","location":"5077be4e-d940-46c5-99d4-c8e55a565933","modified":1686177538514,"created":1686177538514,"persisted":1686177538514},"6e15a93e-aafb-4c7f-8566-7d815f20c703":{"identifier":{"namespace":"","key":"6e15a93e-aafb-4c7f-8566-7d815f20c703"},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"name":"Navcam Left","type":"example.imagery","location":"7112d03a-b858-45ed-9dd4-e854f06b2139","modified":1686177538586,"id":"6e15a93e-aafb-4c7f-8566-7d815f20c703","created":1686177538586,"persisted":1686177538586},"b7e79486-4a78-4f49-aaf3-e750bb7a9d42":{"identifier":{"namespace":"","key":"b7e79486-4a78-4f49-aaf3-e750bb7a9d42"},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"name":"Navcam Right","type":"example.imagery","location":"7112d03a-b858-45ed-9dd4-e854f06b2139","modified":1686177538592,"created":1686177538592,"persisted":1686177538592},"60ef2584-278f-44f1-8358-f5a259acecd2":{"identifier":{"namespace":"","key":"60ef2584-278f-44f1-8358-f5a259acecd2"},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"name":"Hazcam RR","type":"example.imagery","location":"7112d03a-b858-45ed-9dd4-e854f06b2139","modified":1686177538597,"created":1686177538597,"persisted":1686177538597},"ad937768-fa10-4aab-bf05-95f1a0d3d731":{"identifier":{"namespace":"","key":"ad937768-fa10-4aab-bf05-95f1a0d3d731"},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"name":"Hazcam RL","type":"example.imagery","location":"7112d03a-b858-45ed-9dd4-e854f06b2139","modified":1686177538601,"created":1686177538601,"persisted":1686177538601},"ef9132ce-6231-4e5d-a191-5109abd363a1":{"identifier":{"namespace":"","key":"ef9132ce-6231-4e5d-a191-5109abd363a1"},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"name":"Hazcam FL","type":"example.imagery","location":"7112d03a-b858-45ed-9dd4-e854f06b2139","modified":1686177538606,"created":1686177538606,"persisted":1686177538606},"c0995ce5-87e0-4370-a084-889ae334f243":{"identifier":{"namespace":"","key":"c0995ce5-87e0-4370-a084-889ae334f243"},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"name":"Hazcam FR","type":"example.imagery","location":"7112d03a-b858-45ed-9dd4-e854f06b2139","modified":1686177538610,"created":1686177538610,"persisted":1686177538611},"33ec77c3-6725-4aee-8fc3-d57901534d45":{"identifier":{"key":"33ec77c3-6725-4aee-8fc3-d57901534d45","namespace":""},"name":"Navcam Left","type":"example.imagery","configuration":{"imageLocation":"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[]},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"modified":1686178378460,"location":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","created":1686177538554,"persisted":1686178378460},"fdd79f5e-a032-4d66-8552-acbc693eb1b2":{"identifier":{"key":"fdd79f5e-a032-4d66-8552-acbc693eb1b2","namespace":""},"name":"Navcam Right","type":"example.imagery","configuration":{"imageLocation":"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[]},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"location":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","modified":1686178378464,"created":1686177538558,"persisted":1686178378464},"1ce0346e-11aa-4a55-92f7-9d261ae86766":{"identifier":{"key":"1ce0346e-11aa-4a55-92f7-9d261ae86766","namespace":""},"name":"example-imagery-memory-leak-test","type":"example.imagery","configuration":{"imageLocation":"https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg","imageLoadDelayInMilliSeconds":"10000","imageSamples":[],"layers":[]},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"location":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","modified":1686178378445,"created":1686177538537,"persisted":1686178378445},"effa0531-982a-4296-8252-43acc68dc973":{"identifier":{"key":"effa0531-982a-4296-8252-43acc68dc973","namespace":""},"name":"Hazcam FR","type":"example.imagery","configuration":{"imageLocation":"https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg","imageLoadDelayInMilliSeconds":"15000","imageSamples":[],"layers":[]},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"location":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","modified":1686178378448,"created":1686177538541,"persisted":1686178378448},"da8a5f5a-23ad-44e7-8360-612a542f4ba8":{"identifier":{"key":"da8a5f5a-23ad-44e7-8360-612a542f4ba8","namespace":""},"name":"Hazcam RL","type":"example.imagery","configuration":{"imageLocation":"https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg","imageLoadDelayInMilliSeconds":20000,"imageSamples":[],"layers":[]},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"location":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","modified":1686178378452,"created":1686177538546,"persisted":1686178378452},"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2":{"identifier":{"key":"1e39b731-bf19-4d1e-8aa7-2ead2be2d9c2","namespace":""},"name":"Hazcam RR","type":"example.imagery","configuration":{"imageLocation":"https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg,\nhttps://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg","imageLoadDelayInMilliSeconds":"17000","imageSamples":[],"layers":[]},"telemetry":{"values":[{"name":"Name","key":"name","source":"name","hints":{"priority":0}},{"name":"Time","key":"utc","format":"utc","hints":{"domain":2,"priority":1},"source":"utc"},{"name":"Local Time","key":"local","format":"local-format","hints":{"domain":1,"priority":2},"source":"local"},{"name":"Image","key":"url","format":"image","hints":{"image":1,"priority":3},"source":"url"},{"name":"Image Download Name","key":"imageDownloadName","format":"imageDownloadName","hints":{"imageDownloadName":1,"priority":4},"source":"imageDownloadName"}]},"location":"7fa4017a-16fd-4fe1-9b7e-45f0408201b3","modified":1686178378456,"created":1686177538550,"persisted":1686178378456},"57da4a09-4db6-4022-b7f9-e5a5b963600a":{"identifier":{"key":"57da4a09-4db6-4022-b7f9-e5a5b963600a","namespace":""},"name":"Mobility System","type":"conditionSet","configuration":{"conditionTestData":[],"conditionCollection":[{"id":"be20ffef-b80e-491d-8f4e-fbd14a104926","configuration":{"name":"MOBILE","output":"MOBILE","trigger":"all","criteria":[{"id":"9e751e8c-9ed0-4676-b19d-796f3b55febb","telemetry":{"namespace":"","key":"4174ecc6-0af1-4e46-bc3a-fe4d8703561e"},"operation":"greaterThan","input":["0"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Joint Velocity Sine > 0 "},{"isDefault":true,"id":"7cf90ae1-4742-476a-a81b-f6d5bc2feb58","configuration":{"name":"STATIONARY","output":"STATIONARY","trigger":"all","criteria":[]},"summary":""}]},"composition":[{"namespace":"","key":"4174ecc6-0af1-4e46-bc3a-fe4d8703561e"}],"telemetry":{},"modified":1686177538503,"location":"5077be4e-d940-46c5-99d4-c8e55a565933","created":1686177538503,"persisted":1686177538504},"9f2ed308-18d6-4e86-a25f-26c9196788ce":{"identifier":{"key":"9f2ed308-18d6-4e86-a25f-26c9196788ce","namespace":""},"name":"Battery System","type":"conditionSet","configuration":{"conditionTestData":[],"conditionCollection":[{"id":"735765b4-bf8f-4a54-b31d-3fdf6282503e","configuration":{"name":"Good Charge","output":"BATT. GOOD","trigger":"all","criteria":[{"id":"cef3f1fa-77f4-4284-a299-19c82803fb50","telemetry":{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"},"operation":"greaterThan","input":["50"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Battery SOC Sine > 50 "},{"id":"d849fc0b-a42d-4551-b456-cd0d6a35f216","configuration":{"name":"Moderate Charge","output":"BATT. MODERATE","trigger":"all","criteria":[{"id":"905362dd-0b55-4955-a768-c8597651cfdb","telemetry":{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"},"operation":"greaterThan","input":["20"],"metadata":"sin"},{"id":"c3e3a90d-3cfc-4e47-bfff-377dc142a367","telemetry":{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"},"operation":"lessThan","input":["50"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Battery SOC Sine > 20 and Battery SOC Sine < 50 "},{"id":"0d5c4526-37bf-449c-bf21-4aaec93b088c","configuration":{"name":"Marginal Charge","output":"BATT. MARGINAL","trigger":"all","criteria":[{"id":"3247c741-26cb-49b0-97ed-dc8cc9c4df33","telemetry":{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"},"operation":"greaterThan","input":["0"],"metadata":"sin"},{"id":"8124207a-c2a9-4fc4-bec9-77af2b713b50","telemetry":{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"},"operation":"greaterThanOrEq","input":["20"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Battery SOC Sine > 0 and Battery SOC Sine >= 20 "},{"isDefault":true,"id":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","configuration":{"name":"Default","output":"BATT. ERROR","trigger":"all","criteria":[]},"summary":""}]},"composition":[{"namespace":"","key":"8b7c308c-b71a-4189-a414-c9a4eab4b62c"}],"telemetry":{},"modified":1686177538471,"location":"5077be4e-d940-46c5-99d4-c8e55a565933","created":1686177538471,"persisted":1686177538471},"d28608ef-6389-4d9b-98a4-2736f7bc511a":{"identifier":{"key":"d28608ef-6389-4d9b-98a4-2736f7bc511a","namespace":""},"name":"Rover Roll","type":"conditionSet","configuration":{"conditionTestData":[],"conditionCollection":[{"id":"56c960e7-a5a5-43c1-b396-3e21889015ee","configuration":{"name":"DANGER","output":"ROLL DANGER","trigger":"all","criteria":[{"id":"7399459d-c932-4868-b1e8-77eaf9173c3a","telemetry":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"operation":"greaterThan","input":["15"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Rover Roll Sine > 15 "},{"id":"04ee9247-e704-4c1a-a377-c572f93e9b44","configuration":{"name":"DANGER LOW","output":"ROLL DANGER","trigger":"all","criteria":[{"id":"f33ac547-868d-49b6-aa1d-1ad3228e6a21","telemetry":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"operation":"lessThan","input":["-15"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Rover Roll Sine < -15 "},{"id":"6ca762eb-343d-4757-b219-dbbac8a387e6","configuration":{"name":"WARNING","output":"ROLL WARN.","trigger":"all","criteria":[{"id":"6cb35ae7-4a6f-49c5-a30a-d05a22ee4f4e","telemetry":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"operation":"between","input":["10","15"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Rover Roll Sine is between 10 and 15 "},{"id":"497ec5d7-75fb-429c-b16c-70dc8200f540","configuration":{"name":"WARNING LOW","output":"ROLL WARN.","trigger":"all","criteria":[{"id":"e01fcd54-994d-47da-821e-ab2ec6bb9c1c","telemetry":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"operation":"between","input":["-10","-15"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Rover Roll Sine is between -10 and -15 "},{"id":"1184d0cc-555d-46ae-a48b-72ee8264934e","configuration":{"name":"OK","output":"ROLL OK","trigger":"all","criteria":[{"id":"695fca7b-800e-40ad-b85a-3881739744d1","telemetry":{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"},"operation":"between","input":["-10","10"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Rover Roll Sine is between -10 and 10 "},{"isDefault":true,"id":"5f345f62-eed3-42d0-8c88-0e654b5c378e","configuration":{"name":"Default","output":"ROLL ERR.","trigger":"all","criteria":[]},"summary":""}]},"composition":[{"namespace":"","key":"3cc398f7-b45b-4e29-9d8a-792e866c3c41"}],"telemetry":{},"modified":1686177538485,"location":"5077be4e-d940-46c5-99d4-c8e55a565933","created":1686177538485,"persisted":1686177538485},"8d5068d3-9eca-4609-a1c0-8b2591212af7":{"identifier":{"key":"8d5068d3-9eca-4609-a1c0-8b2591212af7","namespace":""},"name":"Thermal System","type":"conditionSet","configuration":{"conditionTestData":[],"conditionCollection":[{"id":"735765b4-bf8f-4a54-b31d-3fdf6282503e","configuration":{"name":"Good","output":"THERMAL GOOD","trigger":"all","criteria":[{"id":"cef3f1fa-77f4-4284-a299-19c82803fb50","telemetry":{"namespace":"","key":"a2db8355-e581-4dec-ae79-47947b98af6f"},"operation":"greaterThan","input":["50"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Battery SOC Sine > 50 "},{"id":"d849fc0b-a42d-4551-b456-cd0d6a35f216","configuration":{"name":"Moderate Charge","output":"THERMAL WARN.","trigger":"all","criteria":[{"id":"905362dd-0b55-4955-a768-c8597651cfdb","telemetry":{"namespace":"","key":"a2db8355-e581-4dec-ae79-47947b98af6f"},"operation":"greaterThan","input":["20"],"metadata":"sin"},{"id":"c3e3a90d-3cfc-4e47-bfff-377dc142a367","telemetry":{"namespace":"","key":"a2db8355-e581-4dec-ae79-47947b98af6f"},"operation":"lessThan","input":["50"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Battery SOC Sine > 20 and Battery SOC Sine < 50 "},{"id":"0d5c4526-37bf-449c-bf21-4aaec93b088c","configuration":{"name":"Marginal Charge","output":"THERMAL DANGER","trigger":"all","criteria":[{"id":"3247c741-26cb-49b0-97ed-dc8cc9c4df33","telemetry":{"namespace":"","key":"a2db8355-e581-4dec-ae79-47947b98af6f"},"operation":"greaterThan","input":["0"],"metadata":"sin"},{"id":"8124207a-c2a9-4fc4-bec9-77af2b713b50","telemetry":{"namespace":"","key":"a2db8355-e581-4dec-ae79-47947b98af6f"},"operation":"greaterThanOrEq","input":["20"],"metadata":"sin"}]},"summary":"Match if all criteria are met: Battery SOC Sine > 0 and Battery SOC Sine >= 20 "},{"isDefault":true,"id":"7331d95a-aa7b-44c3-bf46-c9f78c64c344","configuration":{"name":"Default","output":"BATT. ERROR","trigger":"all","criteria":[]},"summary":""}]},"composition":[{"key":"a2db8355-e581-4dec-ae79-47947b98af6f","namespace":""}],"telemetry":{},"location":"5077be4e-d940-46c5-99d4-c8e55a565933","modified":1686177538523,"created":1686177538523,"persisted":1686177538523},"a2db8355-e581-4dec-ae79-47947b98af6f":{"identifier":{"key":"a2db8355-e581-4dec-ae79-47947b98af6f","namespace":""},"telemetry":{"period":10000000,"amplitude":100,"offset":100,"dataRateInHz":1,"phase":0,"randomness":0.001},"name":"Battery SOC","type":"generator","id":"8b7c308c-b71a-4189-a414-c9a4eab4b62c","location":"8d5068d3-9eca-4609-a1c0-8b2591212af7","modified":1686177538528,"created":1686177538528,"persisted":1686177538528}},"rootId":"40c410d9-ffd3-4f86-80b6-254155abad47"} \ No newline at end of file diff --git a/e2e/tests/performance/imagery.perf.spec.js b/e2e/tests/performance/contract/imagery.contract.perf.spec.js similarity index 100% rename from e2e/tests/performance/imagery.perf.spec.js rename to e2e/tests/performance/contract/imagery.contract.perf.spec.js diff --git a/e2e/tests/performance/notebook.perf.spec.js b/e2e/tests/performance/contract/notebook.contract.perf.spec.js similarity index 100% rename from e2e/tests/performance/notebook.perf.spec.js rename to e2e/tests/performance/contract/notebook.contract.perf.spec.js diff --git a/e2e/tests/performance/memleak-imagery.perf.spec.js b/e2e/tests/performance/memleak-imagery.perf.spec.js deleted file mode 100644 index 2c7dd798f8..0000000000 --- a/e2e/tests/performance/memleak-imagery.perf.spec.js +++ /dev/null @@ -1,121 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2023, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -/* -This test suite is an initial example for memory leak testing using performance. This configuration and execution must -be kept separate from the traditional performance measurements to avoid any "observer" effects associated with tracing -or profiling playwright and/or the browser. - -Based on a pattern identified in https://github.com/trentmwillis/devtools-protocol-demos/blob/master/testing-demos/memory-leak-by-heap.js -and https://github.com/paulirish/automated-chrome-profiling/issues/3 - -Best path forward: https://github.com/cowchimp/headless-devtools/blob/master/src/Memory/example.js - -*/ - -const { test, expect } = require('@playwright/test'); - -const filePath = 'e2e/test-data/PerformanceDisplayLayout.json'; - -// eslint-disable-next-line playwright/no-skipped-test -test.describe.skip('Memory Performance tests', () => { - test.beforeEach(async ({ page, browser }, testInfo) => { - // Go to baseURL - await page.goto('./', { waitUntil: 'networkidle' }); - - // Click a:has-text("My Items") - await page.locator('a:has-text("My Items")').click({ - button: 'right' - }); - - // Click text=Import from JSON - await page.locator('text=Import from JSON').click(); - - // Upload Performance Display Layout.json - await page.setInputFiles('#fileElem', filePath); - - // Click text=OK - await page.locator('text=OK').click(); - - await expect( - page.locator('a:has-text("Performance Display Layout Display Layout")') - ).toBeVisible(); - }); - - test('Embedded View Large for Imagery is performant in Fixed Time', async ({ page, browser }) => { - await page.goto('./', { waitUntil: 'networkidle' }); - - // To to Search Available after Launch - await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); - // Fill Search input - await page - .locator('[aria-label="OpenMCT Search"] input[type="search"]') - .fill('Performance Display Layout'); - //Search Result Appears and is clicked - await Promise.all([ - page.waitForNavigation(), - page.locator('a:has-text("Performance Display Layout")').first().click() - ]); - - //Time to Example Imagery Frame loads within Display Layout - await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' }); - //Time to Example Imagery object loads - await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' }); - - const client = await page.context().newCDPSession(page); - await client.send('HeapProfiler.enable'); - await client.send('HeapProfiler.startSampling'); - // await client.send('HeapProfiler.collectGarbage'); - await client.send('Performance.enable'); - - let performanceMetricsBefore = await client.send('Performance.getMetrics'); - console.log(performanceMetricsBefore.metrics); - - //await client.send('Performance.disable'); - - //Open Large view - await page.locator('button:has-text("Large View")').click(); - await client.send('HeapProfiler.takeHeapSnapshot'); - - //Time to Imagery Rendered in Large Frame - await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' }); - - //Time to Example Imagery object loads - await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' }); - - // Click Close Icon - await page.locator('.c-click-icon').click(); - - //Time to Example Imagery Frame loads within Display Layout - await page.waitForSelector('.c-imagery__main-image__bg', { state: 'visible' }); - //Time to Example Imagery object loads - await page.waitForSelector('.c-imagery__main-image__background-image', { state: 'visible' }); - - await client.send('HeapProfiler.collectGarbage'); - //await client.send('Performance.enable'); - - let performanceMetricsAfter = await client.send('Performance.getMetrics'); - console.log(performanceMetricsAfter.metrics); - - //await client.send('Performance.disable'); - }); -}); diff --git a/e2e/tests/performance/memory/navigation.memory.perf.spec.js b/e2e/tests/performance/memory/navigation.memory.perf.spec.js new file mode 100644 index 0000000000..c781f0051f --- /dev/null +++ b/e2e/tests/performance/memory/navigation.memory.perf.spec.js @@ -0,0 +1,299 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2023, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +const { test, expect } = require('@playwright/test'); + +const memoryLeakFilePath = 'e2e/test-data/memory-leak-detection.json'; +/** + * Executes tests to verify that views are not leaking memory on navigation away. This sort of + * memory leak is generally caused by a failure to clean up registered listeners. + * + * These tests are executed on a set of pre-built displays loaded from ../test-data/memory-leak-detection.json. + * + * In order to modify the test data set: + * 1. Run Open MCT locally (npm start) + * 2. Right click on a folder in the tree, and select "Import From JSON" + * 3. In the subsequent dialog, select the file ../test-data/memory-leak-detection.json + * 4. Click "OK" + * 5. Modify test objects as desired + * 6. Right click on the "Memory Leak Detection" folder, and select "Export to JSON" + * 7. Copy the exported file to ../test-data/memory-leak-detection.json + * + */ + +const NAV_LEAK_TIMEOUT = 10 * 1000; // 10s +test.describe('Navigation memory leak is not detected in', () => { + test.beforeEach(async ({ page }) => { + // Go to baseURL + await page.goto('./', { waitUntil: 'domcontentloaded' }); + + await page.locator('a:has-text("My Items")').click({ + button: 'right' + }); + + await page.locator('text=Import from JSON').click(); + + // Upload memory-leak-detection.json + await page.setInputFiles('#fileElem', memoryLeakFilePath); + + await page.locator('text=OK').click(); + + await expect(page.locator('a:has-text("Memory Leak Detection")')).toBeVisible(); + }); + + test('plot view', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak(page, 'overlay-plot-single-1hz-swg', { + timeout: NAV_LEAK_TIMEOUT + }); + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('stacked plot view', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak(page, 'stacked-plot-single-1hz-swg', { + timeout: NAV_LEAK_TIMEOUT + }); + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('LAD table view', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak(page, 'lad-table-single-1hz-swg', { + timeout: NAV_LEAK_TIMEOUT + }); + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('LAD table set', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak(page, 'lad-table-set-single-1hz-swg', { + timeout: NAV_LEAK_TIMEOUT + }); + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + //TODO: Figure out why using the `table-row` component inside the `table` component leaks TelemetryTableRow objects + test('telemetry table view', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'telemetry-table-single-1hz-swg', + { + timeout: NAV_LEAK_TIMEOUT + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + //TODO: Figure out why using the `SideBar` component inside the leaks Notebook objects + test('notebook view', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'notebook-memory-leak-detection-test', + { + timeout: NAV_LEAK_TIMEOUT + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('display layout of a single SWG alphanumeric', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'display-layout-single-1hz-swg', + { + timeout: NAV_LEAK_TIMEOUT + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('display layout of a single SWG plot', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'display-layout-single-overlay-plot', + { + timeout: NAV_LEAK_TIMEOUT + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + //TODO: Figure out why `svg` in the CompassRose component leaks imagery + test('example imagery view', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'example-imagery-memory-leak-test', + { + timeout: NAV_LEAK_TIMEOUT * 6 // 1 min + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('display layout of example imagery views', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'display-layout-images-memory-leak-test', + { + timeout: NAV_LEAK_TIMEOUT * 6 // 1 min + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('display layout with plots of swgs, alphanumerics, and condition sets, ', async ({ + page + }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'display-layout-simple-telemetry', + { + timeout: NAV_LEAK_TIMEOUT * 6 // 1 min + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('flexible layout with plots of swgs', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'flexible-layout-plots-memory-leak-test', + { + timeout: NAV_LEAK_TIMEOUT + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('flexible layout of example imagery views', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'flexible-layout-images-memory-leak-test', + { + timeout: NAV_LEAK_TIMEOUT * 6 // 1 min + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('tabbed view of display layouts and time strips', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'tab-view-simple-memory-leak-test', + { + timeout: NAV_LEAK_TIMEOUT * 6 * 2 // 2 min + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + test('time strip view of telemetry', async ({ page }) => { + const result = await navigateToObjectAndDetectMemoryLeak( + page, + 'time-strip-telemetry-memory-leak-test', + { + timeout: NAV_LEAK_TIMEOUT * 6 // 1 min + } + ); + + // If we got here without timing out, then the root view object was garbage collected and no memory leak was detected. + expect(result).toBe(true); + }); + + /** + * + * @param {import('@playwright/test').Page} page + * @param {*} objectName + * @returns + */ + async function navigateToObjectAndDetectMemoryLeak(page, objectName) { + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').click(); + // Fill Search input + await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill(objectName); + + //Search Result Appears and is clicked + await Promise.all([ + page.locator(`div.c-gsearch-result__title:has-text("${objectName}")`).first().click(), + page.waitForNavigation() + ]); + + // Register a finalization listener on the root node for the view. This tends to be the last thing to be + // garbage collected since it has either direct or indirect references to all resources used by the view. Therefore it's a pretty good proxy + // 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, + 'navigatedObject', + window.openmct.layout.$refs.browseObject.$refs.objectViewWrapper.firstChild + ); + }); + }); + + // Nav back to folder + await page.goto('./#/browse/mine', { waitUntil: 'networkidle' }); + await page.waitForNavigation(); + + // This next code block blocks until the finalization listener is called and the gcPromise resolved. This means that the root node for the view has been garbage collected. + // In the event that the root node is not garbage collected, the gcPromise will never resolve and the test will time out. + await page.evaluate(() => { + const gcPromise = window.gcPromise; + window.gcPromise = null; + + // Manually invoke the garbage collector once all references are removed. + window.gc(); + + return gcPromise; + }); + + // Clean up the finalization registry since we don't need it any more. + await page.evaluate(() => { + window.fr = null; + }); + + // If we get here without timing out, it means the garbage collection promise resolved and the test passed. + return true; + } +}); diff --git a/package.json b/package.json index 174cf015be..d45a9dd01e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "clean": "rm -rf ./dist ./node_modules ./package-lock.json ./coverage ./html-test-results ./test-results ./.nyc_output ", "clean-test-lint": "npm run clean; npm install; npm run test; npm run lint", "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", "lint": "eslint example src e2e --ext .js openmct.js --max-warnings=0 && eslint example src --ext .vue", "lint:spelling": "cspell \"**/*.{js,md,vue}\" --show-context --gitignore", @@ -101,7 +102,9 @@ "test:e2e:visual:full": "percy exec --config ./e2e/.percy.nightly.yml -- npx playwright test --config=e2e/playwright-visual.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-ci.config.js", - "test:perf": "npx playwright test --config=e2e/playwright-performance.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", "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\\-2023/gm'", "cov:e2e:report": "nyc report --reporter=lcovonly --report-dir=./coverage/e2e", diff --git a/src/api/actions/ActionsAPI.js b/src/api/actions/ActionsAPI.js index f27c402127..ab474701ca 100644 --- a/src/api/actions/ActionsAPI.js +++ b/src/api/actions/ActionsAPI.js @@ -92,7 +92,7 @@ class ActionsAPI extends EventEmitter { if (this._actionCollections.has(key)) { let actionCollection = this._actionCollections.get(key); actionCollection.off('destroy', this._updateCachedActionCollections); - + delete actionCollection.applicableActions; this._actionCollections.delete(key); } } diff --git a/src/api/composition/CompositionProvider.js b/src/api/composition/CompositionProvider.js index 499e8e7157..19b733baa0 100644 --- a/src/api/composition/CompositionProvider.js +++ b/src/api/composition/CompositionProvider.js @@ -185,9 +185,10 @@ export default class CompositionProvider { return; } - this.#publicAPI.objects.eventEmitter.on('mutation', this.#onMutation.bind(this)); + const onMutation = this.#onMutation.bind(this); + this.#publicAPI.objects.eventEmitter.on('mutation', onMutation); this.topicListener = () => { - this.#publicAPI.objects.eventEmitter.off('mutation', this.#onMutation.bind(this)); + this.#publicAPI.objects.eventEmitter.off('mutation', onMutation); }; } diff --git a/src/plugins/charts/bar/BarGraphPlot.vue b/src/plugins/charts/bar/BarGraphPlot.vue index 623a4e23ba..b77de35b02 100644 --- a/src/plugins/charts/bar/BarGraphPlot.vue +++ b/src/plugins/charts/bar/BarGraphPlot.vue @@ -98,7 +98,7 @@ export default { }, beforeUnmount() { if (this.plotResizeObserver) { - this.plotResizeObserver.unobserve(this.$refs.plotWrapper); + this.plotResizeObserver.disconnect(); clearTimeout(this.resizeTimer); } diff --git a/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue b/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue index cef37370ed..490c345b5f 100644 --- a/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue +++ b/src/plugins/charts/scatter/ScatterPlotWithUnderlay.vue @@ -115,7 +115,7 @@ export default { } if (this.plotResizeObserver) { - this.plotResizeObserver.unobserve(this.$refs.plotWrapper); + this.plotResizeObserver.disconnect(); clearTimeout(this.resizeTimer); } diff --git a/src/plugins/condition/Condition.js b/src/plugins/condition/Condition.js index 57d522dbbb..3d48aaa0be 100644 --- a/src/plugins/condition/Condition.js +++ b/src/plugins/condition/Condition.js @@ -65,6 +65,9 @@ export default class Condition extends EventEmitter { this.trigger = conditionConfiguration.configuration.trigger; this.summary = ''; + this.handleCriterionUpdated = this.handleCriterionUpdated.bind(this); + this.handleOldTelemetryCriterion = this.handleOldTelemetryCriterion.bind(this); + this.handleTelemetryStaleness = this.handleTelemetryStaleness.bind(this); } updateResult(datum) { @@ -195,15 +198,15 @@ export default class Condition extends EventEmitter { if (found) { const newCriterionConfiguration = this.generateCriterion(criterionConfiguration); let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct); - newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); - newCriterion.on('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj)); - newCriterion.on('telemetryStaleness', () => this.handleTelemetryStaleness()); + newCriterion.on('criterionUpdated', this.handleCriterionUpdated); + newCriterion.on('telemetryIsOld', this.handleOldTelemetryCriterion); + newCriterion.on('telemetryStaleness', this.handleTelemetryStaleness); let criterion = found.item; criterion.unsubscribe(); - criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); - criterion.off('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj)); - newCriterion.off('telemetryStaleness', () => this.handleTelemetryStaleness()); + criterion.off('criterionUpdated', this.handleCriterionUpdated); + criterion.off('telemetryIsOld', this.handleOldTelemetryCriterion); + newCriterion.off('telemetryStaleness', this.handleTelemetryStaleness); this.criteria.splice(found.index, 1, newCriterion); } } @@ -212,9 +215,9 @@ export default class Condition extends EventEmitter { let found = this.findCriterion(id); if (found) { let criterion = found.item; - criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj)); - criterion.off('telemetryIsOld', (obj) => this.handleOldTelemetryCriterion(obj)); - criterion.off('telemetryStaleness', () => this.handleTelemetryStaleness()); + criterion.off('criterionUpdated', this.handleCriterionUpdated); + criterion.off('telemetryIsOld', this.handleOldTelemetryCriterion); + criterion.off('telemetryStaleness', this.handleTelemetryStaleness); criterion.destroy(); this.criteria.splice(found.index, 1); diff --git a/src/plugins/condition/components/ConditionCollection.vue b/src/plugins/condition/components/ConditionCollection.vue index ae6806c757..c0b55442b7 100644 --- a/src/plugins/condition/components/ConditionCollection.vue +++ b/src/plugins/condition/components/ConditionCollection.vue @@ -127,6 +127,7 @@ export default { this.composition.off('remove', this.removeTelemetryObject); if (this.conditionManager) { this.conditionManager.off('conditionSetResultUpdated', this.handleConditionSetResultUpdated); + this.conditionManager.off('noTelemetryObjects', this.emitNoTelemetryObjectEvent); this.conditionManager.destroy(); } diff --git a/src/plugins/conditionWidget/components/ConditionWidget.vue b/src/plugins/conditionWidget/components/ConditionWidget.vue index feeb261a5f..dd9c8d0e76 100644 --- a/src/plugins/conditionWidget/components/ConditionWidget.vue +++ b/src/plugins/conditionWidget/components/ConditionWidget.vue @@ -81,7 +81,7 @@ export default { this.listenToConditionSetChanges(); } }, - beforeUnmount() { + unmounted() { this.stopListeningToConditionSetChanges(); }, methods: { diff --git a/src/plugins/displayLayout/DisplayLayoutToolbar.js b/src/plugins/displayLayout/DisplayLayoutToolbar.js index 5e7d4f315f..1e73e3b465 100644 --- a/src/plugins/displayLayout/DisplayLayoutToolbar.js +++ b/src/plugins/displayLayout/DisplayLayoutToolbar.js @@ -170,7 +170,7 @@ define(['lodash'], function (_) { if (form) { showForm(form, name, selectionPath); } else { - selectionPath[0].context.addElement(name); + openmct.objectViews.emit('contextAction', 'addElement', name); } }, key: 'add', @@ -236,7 +236,6 @@ define(['lodash'], function (_) { icon: 'icon-trash', title: 'Delete the selected object', method: function () { - let removeItem = selectionPath[1].context.removeItem; let prompt = openmct.overlays.dialog({ iconClass: 'alert', message: `Warning! This action will remove this item from the Display Layout. Do you want to continue?`, @@ -245,7 +244,11 @@ define(['lodash'], function (_) { label: 'OK', emphasis: 'true', callback: function () { - removeItem(getAllTypes(selection)); + openmct.objectViews.emit( + 'contextAction', + 'removeItem', + getAllTypes(selection) + ); prompt.dismiss(); } }, @@ -290,7 +293,12 @@ define(['lodash'], function (_) { } ], method: function (option) { - selectionPath[1].context.orderItem(option.value, getAllTypes(selectedObjects)); + openmct.objectViews.emit( + 'contextAction', + 'orderItem', + option.value, + getAllTypes(selectedObjects) + ); } }; } @@ -474,9 +482,7 @@ define(['lodash'], function (_) { icon: 'icon-duplicate', title: 'Duplicate the selected object', method: function () { - let duplicateItem = selectionPath[1].context.duplicateItem; - - duplicateItem(selection); + openmct.objectViews.emit('contextAction', 'duplicateItem', selection); } }; } @@ -555,6 +561,7 @@ define(['lodash'], function (_) { function getViewSwitcherMenu(selectedParent, selectionPath, selection) { if (selection.length === 1) { + // eslint-disable-next-line no-unused-vars let displayLayoutContext = selectionPath[1].context; let selectedItemContext = selectionPath[0].context; let selectedItemType = selectedItemContext.item.type; @@ -574,14 +581,18 @@ define(['lodash'], function (_) { label: 'View type', options: viewOptions, method: function (option) { - displayLayoutContext.switchViewType(selectedItemContext, option.value, selection); + openmct.objectViews.emit( + 'contextAction', + 'switchViewType', + selectedItemContext, + option.value, + selection + ); } }; } } else if (selection.length > 1) { if (areAllViews('telemetry-view', 'layoutItem.type', selection)) { - let displayLayoutContext = selectionPath[1].context; - return { control: 'menu', domainObject: selectedParent, @@ -590,12 +601,15 @@ define(['lodash'], function (_) { label: 'View type', options: APPLICABLE_VIEWS['telemetry-view-multi'], method: function (option) { - displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value); + openmct.objectViews.emit( + 'contextAction', + 'mergeMultipleTelemetryViews', + selection, + option.value + ); } }; } else if (areAllViews('telemetry.plot.overlay', 'item.type', selection)) { - let displayLayoutContext = selectionPath[1].context; - return { control: 'menu', domainObject: selectedParent, @@ -603,7 +617,12 @@ define(['lodash'], function (_) { title: 'Merge into a stacked plot', options: APPLICABLE_VIEWS['telemetry.plot.overlay-multi'], method: function (option) { - displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value); + openmct.objectViews.emit( + 'contextAction', + 'mergeMultipleOverlayPlots', + selection, + option.value + ); } }; } @@ -627,7 +646,7 @@ define(['lodash'], function (_) { domainObject: displayLayoutContext.item, icon: ICON_GRID_SHOW, method: function () { - displayLayoutContext.toggleGrid(); + openmct.objectViews.emit('contextAction', 'toggleGrid'); this.icon = this.icon === ICON_GRID_SHOW ? ICON_GRID_HIDE : ICON_GRID_SHOW; }, @@ -653,7 +672,7 @@ define(['lodash'], function (_) { function showForm(formStructure, name, selectionPath) { openmct.forms.showForm(formStructure).then((changes) => { - selectionPath[0].context.addElement(name, changes); + openmct.objectViews.emit('contextAction', 'addElement', name, changes); }); } diff --git a/src/plugins/displayLayout/components/BoxView.vue b/src/plugins/displayLayout/components/BoxView.vue index cd3f9462b5..b9c5d6046b 100644 --- a/src/plugins/displayLayout/components/BoxView.vue +++ b/src/plugins/displayLayout/components/BoxView.vue @@ -25,14 +25,16 @@ :item="item" :grid-size="gridSize" :is-editing="isEditing" - @move="(gridDelta) => $emit('move', gridDelta)" - @endMove="() => $emit('endMove')" + @move="move" + @endMove="endMove" > -
+ @@ -115,10 +117,18 @@ export default { this.initSelect ); }, - unmounted() { + beforeUnmount() { if (this.removeSelectable) { this.removeSelectable(); } + }, + methods: { + move(gridDelta) { + this.$emit('move', gridDelta); + }, + endMove() { + this.$emit('endMove'); + } } }; diff --git a/src/plugins/displayLayout/components/DisplayLayout.vue b/src/plugins/displayLayout/components/DisplayLayout.vue index f0ac5ef0d0..ac5bd4fa1a 100644 --- a/src/plugins/displayLayout/components/DisplayLayout.vue +++ b/src/plugins/displayLayout/components/DisplayLayout.vue @@ -144,7 +144,7 @@ function getItemDefinition(itemType, ...options) { export default { components: components, - inject: ['openmct', 'objectPath', 'options', 'objectUtils', 'currentView'], + inject: ['openmct', 'objectPath', 'options', 'currentView'], props: { domainObject: { type: Object, @@ -221,13 +221,21 @@ export default { this.composition.load(); this.gridDimensions = [this.$el.offsetWidth, this.$el.scrollHeight]; - this.openmct.objects.observe(this.domainObject, 'configuration.items', (items) => { - this.layoutItems = items; - }); + this.unObserveItems = this.openmct.objects.observe( + this.domainObject, + 'configuration.items', + (items) => { + this.layoutItems = [...items]; + } + ); this.watchDisplayResize(); }, - unmounted() { + beforeUnmount() { + if (this.unObserveItems) { + this.unObserveItems(); + } + this.unwatchDisplayResize(); this.openmct.selection.off('change', this.setSelection); this.composition.off('add', this.addChild); this.composition.off('remove', this.removeChild); @@ -251,9 +259,15 @@ export default { this.$el.click(); }, watchDisplayResize() { - const resizeObserver = new ResizeObserver(() => this.updateGrid()); + this.unwatchDisplayResize(); + this.resizeObserver = new ResizeObserver(this.updateGrid); - resizeObserver.observe(this.$el); + this.resizeObserver.observe(this.$el); + }, + unwatchDisplayResize() { + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + } }, addElement(itemType, element) { this.addItem(itemType + '-view', element); @@ -623,7 +637,7 @@ export default { return this.openmct.objects.makeKeyString(item.identifier) !== keyString; } }); - this.layoutItems = layoutItems; + this.layoutItems = [...layoutItems]; this.mutate('configuration.items', layoutItems); this.clearSelection(); }, diff --git a/src/plugins/displayLayout/components/EllipseView.vue b/src/plugins/displayLayout/components/EllipseView.vue index 79458d8058..00b1074f48 100644 --- a/src/plugins/displayLayout/components/EllipseView.vue +++ b/src/plugins/displayLayout/components/EllipseView.vue @@ -25,14 +25,16 @@ :item="item" :grid-size="gridSize" :is-editing="isEditing" - @move="(gridDelta) => $emit('move', gridDelta)" - @endMove="() => $emit('endMove')" + @move="move" + @endMove="endMove" > -
+ @@ -115,10 +117,18 @@ export default { this.initSelect ); }, - unmounted() { + beforeUnmount() { if (this.removeSelectable) { this.removeSelectable(); } + }, + methods: { + move(gridDelta) { + this.$emit('move', gridDelta); + }, + endMove() { + this.$emit('endMove'); + } } }; diff --git a/src/plugins/displayLayout/components/ImageView.vue b/src/plugins/displayLayout/components/ImageView.vue index f6b5bc3341..54dd8ffc2e 100644 --- a/src/plugins/displayLayout/components/ImageView.vue +++ b/src/plugins/displayLayout/components/ImageView.vue @@ -25,10 +25,12 @@ :item="item" :grid-size="gridSize" :is-editing="isEditing" - @move="(gridDelta) => $emit('move', gridDelta)" - @endMove="() => $emit('endMove')" + @move="move" + @endMove="endMove" > -
+ @@ -118,10 +120,18 @@ export default { this.initSelect ); }, - unmounted() { + beforeUnmount() { if (this.removeSelectable) { this.removeSelectable(); } + }, + methods: { + move(gridDelta) { + this.$emit('move', gridDelta); + }, + endMove() { + this.$emit('endMove'); + } } }; diff --git a/src/plugins/displayLayout/components/LayoutFrame.vue b/src/plugins/displayLayout/components/LayoutFrame.vue index d81b6b4f02..9e4a01b95e 100644 --- a/src/plugins/displayLayout/components/LayoutFrame.vue +++ b/src/plugins/displayLayout/components/LayoutFrame.vue @@ -29,7 +29,7 @@ }" :style="style" > - +
diff --git a/src/plugins/displayLayout/components/SubobjectView.vue b/src/plugins/displayLayout/components/SubobjectView.vue index d40b626a69..689a1ff5b7 100644 --- a/src/plugins/displayLayout/components/SubobjectView.vue +++ b/src/plugins/displayLayout/components/SubobjectView.vue @@ -20,24 +20,26 @@ at runtime from the About dialog for additional information. --> diff --git a/src/plugins/displayLayout/plugin.js b/src/plugins/displayLayout/plugin.js index afa67107c9..c8c73a98e2 100644 --- a/src/plugins/displayLayout/plugin.js +++ b/src/plugins/displayLayout/plugin.js @@ -20,14 +20,14 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js'; +import mount from 'utils/mount'; + import CopyToClipboardAction from './actions/CopyToClipboardAction'; +import AlphaNumericFormatViewProvider from './AlphanumericFormatViewProvider.js'; import DisplayLayout from './components/DisplayLayout.vue'; import DisplayLayoutToolbar from './DisplayLayoutToolbar.js'; import DisplayLayoutType from './DisplayLayoutType.js'; import DisplayLayoutDrawingObjectTypes from './DrawingObjectTypes.js'; -import objectUtils from 'objectUtils'; -import mount from 'utils/mount'; class DisplayLayoutView { constructor(openmct, domainObject, objectPath, options) { @@ -37,7 +37,6 @@ class DisplayLayoutView { this.options = options; this.component = null; - this.app = null; } show(container, isEditing) { @@ -51,7 +50,6 @@ class DisplayLayoutView { openmct: this.openmct, objectPath: this.objectPath, options: this.options, - objectUtils, currentView: this }, data: () => { @@ -83,20 +81,17 @@ class DisplayLayoutView { getSelectionContext() { return { item: this.domainObject, - supportsMultiSelect: true, - addElement: this.component && this.component.$refs.displayLayout.addElement, - removeItem: this.component && this.component.$refs.displayLayout.removeItem, - orderItem: this.component && this.component.$refs.displayLayout.orderItem, - duplicateItem: this.component && this.component.$refs.displayLayout.duplicateItem, - switchViewType: this.component && this.component.$refs.displayLayout.switchViewType, - mergeMultipleTelemetryViews: - this.component && this.component.$refs.displayLayout.mergeMultipleTelemetryViews, - mergeMultipleOverlayPlots: - this.component && this.component.$refs.displayLayout.mergeMultipleOverlayPlots, - toggleGrid: this.component && this.component.$refs.displayLayout.toggleGrid + supportsMultiSelect: true }; } + contextAction() { + const action = arguments[0]; + if (this.component && this.component.$refs.displayLayout[action]) { + this.component.$refs.displayLayout[action](...Array.from(arguments).splice(1)); + } + } + onEditModeChange(isEditing) { this.component.isEditing = isEditing; } @@ -104,6 +99,7 @@ class DisplayLayoutView { destroy() { if (this._destroy) { this._destroy(); + this.component = undefined; } } } diff --git a/src/plugins/flexibleLayout/components/container.vue b/src/plugins/flexibleLayout/components/container.vue index 915657e88b..02dc191783 100644 --- a/src/plugins/flexibleLayout/components/container.vue +++ b/src/plugins/flexibleLayout/components/container.vue @@ -122,7 +122,6 @@ export default { mounted() { let context = { item: this.$parent.domainObject, - addContainer: this.addContainer, type: 'container', containerId: this.container.id }; diff --git a/src/plugins/flexibleLayout/components/flexibleLayout.vue b/src/plugins/flexibleLayout/components/flexibleLayout.vue index 2932946c70..11b7cb3dfc 100644 --- a/src/plugins/flexibleLayout/components/flexibleLayout.vue +++ b/src/plugins/flexibleLayout/components/flexibleLayout.vue @@ -164,16 +164,32 @@ export default { this.composition.on('remove', this.removeChildObject); this.composition.on('add', this.addFrame); this.composition.load(); - this.openmct.objects.observe(this.domainObject, 'configuration.containers', (containers) => { - this.containers = containers; - }); - this.openmct.objects.observe(this.domainObject, 'configuration.rowsLayout', (rowsLayout) => { - this.rowsLayout = rowsLayout; - }); + this.unObserveContainers = this.openmct.objects.observe( + this.domainObject, + 'configuration.containers', + (containers) => { + this.containers = containers; + } + ); + this.unObserveRowsLayout = this.openmct.objects.observe( + this.domainObject, + 'configuration.rowsLayout', + (rowsLayout) => { + this.rowsLayout = rowsLayout; + } + ); }, beforeUnmount() { this.composition.off('remove', this.removeChildObject); this.composition.off('add', this.addFrame); + + if (this.unObserveContainers) { + this.unObserveContainers(); + } + + if (this.unObserveRowsLayout) { + this.unObserveRowsLayout(); + } }, methods: { containsObject(identifier) { diff --git a/src/plugins/flexibleLayout/components/frame.vue b/src/plugins/flexibleLayout/components/frame.vue index af2831bda6..bc31487669 100644 --- a/src/plugins/flexibleLayout/components/frame.vue +++ b/src/plugins/flexibleLayout/components/frame.vue @@ -142,6 +142,9 @@ export default { childContext.item = this.domainObject; childContext.type = 'frame'; childContext.frameId = this.frame.id; + if (this.unsubscribeSelection) { + this.unsubscribeSelection(); + } this.unsubscribeSelection = this.openmct.selection.selectable( this.$refs.frame, childContext, diff --git a/src/plugins/flexibleLayout/components/resizeHandle.vue b/src/plugins/flexibleLayout/components/resizeHandle.vue index e956f7b987..c961933833 100644 --- a/src/plugins/flexibleLayout/components/resizeHandle.vue +++ b/src/plugins/flexibleLayout/components/resizeHandle.vue @@ -56,7 +56,7 @@ export default { document.addEventListener('dragend', this.unsetDragging); document.addEventListener('drop', this.unsetDragging); }, - unmounted() { + beforeUnmount() { document.removeEventListener('dragstart', this.setDragging); document.removeEventListener('dragend', this.unsetDragging); document.removeEventListener('drop', this.unsetDragging); diff --git a/src/plugins/flexibleLayout/flexibleLayoutViewProvider.js b/src/plugins/flexibleLayout/flexibleLayoutViewProvider.js index ef31d925c5..2d07084431 100644 --- a/src/plugins/flexibleLayout/flexibleLayoutViewProvider.js +++ b/src/plugins/flexibleLayout/flexibleLayoutViewProvider.js @@ -77,12 +77,15 @@ export default class FlexibleLayoutViewProvider { getSelectionContext() { return { item: domainObject, - addContainer: component.$refs.flexibleLayout.addContainer, - deleteContainer: component.$refs.flexibleLayout.deleteContainer, - deleteFrame: component.$refs.flexibleLayout.deleteFrame, type: 'flexible-layout' }; }, + contextAction() { + const action = arguments[0]; + if (component && component.$refs.flexibleLayout[action]) { + component.$refs.flexibleLayout[action](...Array.from(arguments).splice(1)); + } + }, onEditModeChange(isEditing) { component.isEditing = isEditing; }, diff --git a/src/plugins/flexibleLayout/toolbarProvider.js b/src/plugins/flexibleLayout/toolbarProvider.js index 3771191bba..dd7d4fe61f 100644 --- a/src/plugins/flexibleLayout/toolbarProvider.js +++ b/src/plugins/flexibleLayout/toolbarProvider.js @@ -89,8 +89,6 @@ function ToolbarProvider(openmct) { control: 'button', domainObject: primary.context.item, method: function () { - let deleteFrameAction = tertiary.context.deleteFrame; - let prompt = openmct.overlays.dialog({ iconClass: 'alert', message: `This action will remove this frame from this Flexible Layout. Do you want to continue?`, @@ -99,7 +97,11 @@ function ToolbarProvider(openmct) { label: 'OK', emphasis: 'true', callback: function () { - deleteFrameAction(primary.context.frameId); + openmct.objectViews.emit( + 'contextAction', + 'deleteFrame', + primary.context.frameId + ); prompt.dismiss(); } }, @@ -136,7 +138,9 @@ function ToolbarProvider(openmct) { addContainer = { control: 'button', domainObject: tertiary.context.item, - method: tertiary.context.addContainer, + method: function () { + openmct.objectViews.emit('contextAction', 'addContainer', ...arguments); + }, key: 'add', icon: 'icon-plus-in-rect', title: 'Add Container' @@ -152,7 +156,6 @@ function ToolbarProvider(openmct) { control: 'button', domainObject: primary.context.item, method: function () { - let removeContainer = secondary.context.deleteContainer; let containerId = primary.context.containerId; let prompt = openmct.overlays.dialog({ @@ -164,7 +167,7 @@ function ToolbarProvider(openmct) { label: 'OK', emphasis: 'true', callback: function () { - removeContainer(containerId); + openmct.objectViews.emit('contextAction', 'deleteContainer', containerId); prompt.dismiss(); } }, @@ -185,7 +188,9 @@ function ToolbarProvider(openmct) { addContainer = { control: 'button', domainObject: secondary.context.item, - method: secondary.context.addContainer, + method: function () { + openmct.objectViews.emit('contextAction', 'addContainer', ...arguments); + }, key: 'add', icon: 'icon-plus-in-rect', title: 'Add Container' @@ -198,7 +203,9 @@ function ToolbarProvider(openmct) { addContainer = { control: 'button', domainObject: primary.context.item, - method: primary.context.addContainer, + method: function () { + openmct.objectViews.emit('contextAction', 'addContainer', ...arguments); + }, key: 'add', icon: 'icon-plus-in-rect', title: 'Add Container' diff --git a/src/plugins/imagery/components/ImageryView.vue b/src/plugins/imagery/components/ImageryView.vue index a87800d63d..212558e5e6 100644 --- a/src/plugins/imagery/components/ImageryView.vue +++ b/src/plugins/imagery/components/ImageryView.vue @@ -727,7 +727,8 @@ export default { } } - this.stopListening(this.focusedImageWrapper, 'wheel', this.wheelZoom, this); + // remove all eventListeners + this.stopListening(); Object.keys(this.imageryAnnotations).forEach((time) => { const imageAnnotationsForTime = this.imageryAnnotations[time]; @@ -1276,6 +1277,9 @@ export default { this.scrollHandler(); }, setSizedImageDimensions() { + if (!this.$refs.focusedImage) { + return; + } this.focusedImageNaturalAspectRatio = this.$refs.focusedImage.naturalWidth / this.$refs.focusedImage.naturalHeight; if ( diff --git a/src/plugins/imagery/components/ImageryViewMenuSwitcher.vue b/src/plugins/imagery/components/ImageryViewMenuSwitcher.vue index cb07c20276..8fadd00997 100644 --- a/src/plugins/imagery/components/ImageryViewMenuSwitcher.vue +++ b/src/plugins/imagery/components/ImageryViewMenuSwitcher.vue @@ -27,9 +27,7 @@ :class="iconClass" :title="title" @click="toggleMenu" - > - - + />
diff --git a/src/plugins/inspectorDataVisualization/DataVisualization.vue b/src/plugins/inspectorDataVisualization/DataVisualization.vue index 2578f022b2..fa7deae5c4 100644 --- a/src/plugins/inspectorDataVisualization/DataVisualization.vue +++ b/src/plugins/inspectorDataVisualization/DataVisualization.vue @@ -44,7 +44,7 @@ -