Compare commits

...

15 Commits

Author SHA1 Message Date
Jesse Mazzella
de5914b82d almost there!!! 2023-08-02 14:39:46 -07:00
Jesse Mazzella
41bbbb136b 🤷‍♂️ 2023-08-02 13:19:34 -07:00
Jamie V
0f199b6aff created a throttle util and using it in timer plugin to throttle refreshing the timer domain object 2023-08-02 12:29:26 -04:00
Jesse Mazzella
95e686038d fix: toggling markers, alarm markers, marker style + update Vue.extend() usage to Vue 3 (#6868)
* fix: update to `defineComponent` from `Vue.extend()`
* fix: unwrap Proxy arg before WeakMap.get()
* refactor: `defineComponent` not needed here
2023-08-01 14:07:59 -07:00
Shefali Joshi
f705bf9a61 Wait for bounds change to reset telemetry collection data (#6857)
* Reset and re-request telemetry only after receiving bounds following a mode change

* Don't check for tick - just in case the mode is set without bounds

* Use the imagery view timeContext to get related telemetry.

---------

Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
2023-07-31 18:15:08 +00:00
Shefali Joshi
50559ac502 Don't allow editing line more when not editing display layout (#6858) 2023-07-31 10:16:52 -07:00
Jesse Mazzella
f0ef93dd3f fix: remove tree-item-destroyed event (#6856)
* fix: remove `tree-item-destroyed` event

- Composition listeners should only be removed if the item has been deleted or its parent has been collapsed. Both are handled by `mct-tree` already, so doing this additionally when tree-items unmount is redundant.

- In addition to that, any time the `visibleTreeItems` array changes, all tree-items will unmount, so this just doesn't work as intended-- it will unregister all composition listeners whenever the tree changes!

* test: stabilize imagery test

- Use keyboard gestures to navigate

* fix: lint:fix
2023-07-31 11:57:11 -05:00
Jesse Mazzella
3ae14cf786 Revert "[CI] Temporarily disable some tests" (#6853)
* Revert "[CI] Temporarily disable some tests (#6806)"

This reverts commit 85974fc5f1.

* fix(e2e): fix visual tests

* refactor: lint:fix

* fix: revert localStorage data changes

---------

Co-authored-by: Shefali Joshi <simplyrender@gmail.com>
2023-07-28 17:20:06 -07:00
Jesse Mazzella
194eb43607 fix(#6854): [LADTableSet] prevent compositions from becoming reactive (#6855)
* fix: prevent compositions from becoming reactive
2023-07-28 12:35:11 -07:00
Shefali Joshi
3c2b032526 Plan rendering inside a timestrip (#6852)
* Use the width and height of the container of the plan to set the activity widths and now markers

* Use the right parent to determine height and width

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
2023-07-28 10:24:48 -07:00
Shefali Joshi
d4e51cbaf1 Use the current timestamp from the global clock (#6851)
* Use the current timestamp from the global clock. Use mode changes to set if the view is fixed time or real time

* Reload the page after adding a plan and then change the url params.
2023-07-28 09:37:11 -07:00
Khalid Adil
7c58b19c3e Switch staleness provider for SWG to use modeChanged instead of clock (#6845)
* Switch staleness provider for SWG to use modeChanged instead of clock
2023-07-28 05:04:42 +00:00
Jesse Mazzella
16e1ac2529 Fixes for e2e tests following the Vue 3 compat upgrade (#6837)
* clock, timeConductor and appActions fixes

* Ensure realtime uses upstream context when available
Eliminate ambiguity when looking for time conductor locator

* Fix log plot e2e tests

* Fix displayLayout e2e tests

* Specify global time conductor to fix issues with duplicate selectors with independent time contexts

* a11y: ARIA for conductor and independent time conductor

* a11y: fix label collisions, specify 'Menu' in label

* Add watch mode

* fix(e2e): update appActions and tests to use a11y locators for ITC

* Don't remove the itc popup from the DOM. Just show/hide it once it's added the first time.

* test(e2e): disable one imagery test due to known bug

* Add fixme to tagging tests, issue described in 6822

* Fix locator for time conductor popups

* Improve how time bounds are set in independent time conductor.
Fix tests for flexible layout and timestrip

* Fix some tests for itc for display layouts

* Fix Inspector tabs remounting on change

* fix autoscale test and snapshot

* Fix telemetry table test

* Fix timestrip test

* e2e: move test info annotations to within test

* 6826: Fixes padStart error due to using it on a number rather than a string

* fix(e2e): update snapshots

* fix(e2e): fix restricted notebook locator

* fix(restrictedNotebook): fix issue causing sections not to update on lock

* fix(restrictedNotebook): fix issue causing snapshots to not be able to be deleted from a locked page

- Using `this.$delete(arr, index)` does not update the `length` property on the underlying target object, so it can lead to bizarre issues where your array is of length 4 but it has 3 objects in it.

* fix: replace all instances of `$delete` with `Array.splice()` or `delete`

* fix(e2e): fix grand search test

* fix(#3117): can remove item from displayLayout via tree context menu while viewing another item

* fix: remove typo 

* Wait for background image to load

* fix(#6832): timelist events can tick down

* fix: ensure that menuitems have the raw objects so emits work

* fix: assign new arrays instead of editing state in-place

* refactor(timelist): use `getClock()` instead of `clock()`

* Revert "refactor(timelist): use `getClock()` instead of `clock()`"

This reverts commit d888553112.

* refactor(timelist): use new timeAPI

* Stop ticking when the independent time context is disabled (#6833)

* Turn off the clock ticket for independent time conductor when it is disabled

* Fix linting issues

---------

Co-authored-by: Khalid Adil <khalidadil29@gmail.com>

* test: update couchdb notebook test

* fix: codeQL warnings

* fix(tree-item): infinite spinner issue

- Using `indexOf()` with an object was failing due to some items in the tree being Proxy-wrapped and others not. So instead, use `findIndex()` with a predicate that compares the navigationPaths of both objects

* [Timer] Remove "refresh" call, it is not needed (#6841)

* removing an unneccessary refresh that waas causing many get requests
* lets just pretend this never happened

* fix(mct-tree): maintain reactivity of all tree items

* Hide change role button in the indicator in cases where there is only… (#6840)

Hide change role button in the indicator in cases where there is only a single role available for the current user

---------

Co-authored-by: Shefali <simplyrender@gmail.com>
Co-authored-by: Khalid Adil <khalidadil29@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: David Tsay <david.e.tsay@nasa.gov>
Co-authored-by: Jamie V <jamie.j.vigliotta@nasa.gov>
2023-07-28 02:06:41 +00:00
Jesse Mazzella
4885c816dc Migrate to Vue 3 Migration Build (#6767)
* Replacing all instances of the new Vue() component creation pattern
* In Vue 3, components cannot be created on the fly and mounted off-DOM. The suggested fix from Vue is to use createApp, but in the context of Open MCT this means dozens of Vue apps being created and destroyed at any given moment. Instead, we have used a community hack for creating individual components.
* beforeDestroy() -> beforeUnmount()
* destroyed() -> unmounted()
* The addition of deep: true option on Array listeners is now required to detect Array changes
* Open MCT is now mounted on a child div instead of directly on document.body


---------

Co-authored-by: Scott Bell <scott@traclabs.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
2023-07-19 11:22:23 -07:00
Jamie V
42b545917c [Time] Conductors and API Enhancements (#6768)
* Fixed #4975 - Compact Time Conductor styling
* Fixed #5773 - Ubiquitous global clock
* Mode functionality added to TimeAPI
* TimeAPI modified to always have a ticking clock
* Mode dropdown added to independent and regular time conductors
* Overall conductor appearance modifications and enhancements
* TimeAPI methods deprecated with warnings
* Significant updates to markup, styling and behavior of main Time Conductor and independent version.


---------

Co-authored-by: Charles Hacskaylo <charlesh88@gmail.com>
Co-authored-by: Shefali <simplyrender@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: John Hill <john.c.hill@nasa.gov>
Co-authored-by: Scott Bell <scott@traclabs.com>
2023-07-18 17:32:05 -07:00
363 changed files with 7345 additions and 4609 deletions

View File

@@ -242,6 +242,10 @@ workflows:
name: e2e-stable
node-version: lts/hydrogen
suite: stable
- perf-test:
node-version: lts/hydrogen
- visual-test:
node-version: lts/hydrogen
the-nightly: #These jobs do not run on PRs, but against master at night
jobs:

View File

@@ -28,6 +28,8 @@ module.exports = {
}
},
rules: {
'vue/no-v-for-template-key': 'off',
'vue/no-v-for-template-key-on-child': 'error',
'prettier/prettier': 'error',
'you-dont-need-lodash-underscore/omit': 'off',
'you-dont-need-lodash-underscore/throttle': 'off',

View File

@@ -67,7 +67,8 @@ const config = {
MCT: path.join(projectRootDir, 'src/MCT'),
testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'),
objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'),
utils: path.join(projectRootDir, 'src/utils')
utils: path.join(projectRootDir, 'src/utils'),
vue: path.join(projectRootDir, 'node_modules/@vue/compat/dist/vue.esm-bundler.js'),
}
},
plugins: [
@@ -121,7 +122,15 @@ const config = {
},
{
test: /\.vue$/,
use: 'vue-loader'
loader: 'vue-loader',
options: {
compilerOptions: {
whitespace: 'preserve',
compatConfig: {
MODE: 2
}
}
}
},
{
test: /\.html$/,

View File

@@ -25,11 +25,6 @@ module.exports = merge(common, {
'**/.*' // dotfiles and dotfolders
]
},
resolve: {
alias: {
vue: path.join(projectRootDir, 'node_modules/vue/dist/vue.js')
}
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '"dist/"'

View File

@@ -13,11 +13,6 @@ const projectRootDir = path.resolve(__dirname, '..');
module.exports = merge(common, {
mode: 'production',
resolve: {
alias: {
vue: path.join(projectRootDir, 'node_modules/vue/dist/vue.min.js')
}
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_ROOT_RELATIVE__: '""'

6
API.md
View File

@@ -2,7 +2,7 @@
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Building Applications With Open MCT](#developing-applications-with-open-mct)
- [Developing Applications With Open MCT](#developing-applications-with-open-mct)
- [Scope and purpose of this document](#scope-and-purpose-of-this-document)
- [Building From Source](#building-from-source)
- [Starting an Open MCT application](#starting-an-open-mct-application)
@@ -26,7 +26,7 @@
- [Value Hints](#value-hints)
- [The Time Conductor and Telemetry](#the-time-conductor-and-telemetry)
- [Telemetry Providers](#telemetry-providers)
- [Telemetry Requests and Responses.](#telemetry-requests-and-responses)
- [Telemetry Requests and Responses](#telemetry-requests-and-responses)
- [Request Strategies **draft**](#request-strategies-draft)
- [`latest` request strategy](#latest-request-strategy)
- [`minmax` request strategy](#minmax-request-strategy)
@@ -873,6 +873,8 @@ function without any arguments.
#### Stopping an active clock
_As of July 2023, this method will be deprecated. Open MCT will always have a ticking clock._
The `stopClock` method can be used to stop an active clock, and to clear it. It
will stop the clock from ticking, and set the active clock to `undefined`.

View File

@@ -314,13 +314,13 @@ async function _isInEditMode(page, identifier) {
*/
async function setTimeConductorMode(page, isFixedTimespan = true) {
// Click 'mode' button
await page.locator('.c-mode-button').click();
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await page.getByRole('button', { name: 'Time Conductor Mode Menu' }).click();
// Switch time conductor mode
if (isFixedTimespan) {
await page.locator('data-testid=conductor-modeOption-fixed').click();
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
} else {
await page.locator('data-testid=conductor-modeOption-realtime').click();
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
}
}
@@ -342,9 +342,12 @@ async function setRealTimeMode(page) {
/**
* @typedef {Object} OffsetValues
* @property {string | undefined} hours
* @property {string | undefined} mins
* @property {string | undefined} secs
* @property {string | undefined} startHours
* @property {string | undefined} startMins
* @property {string | undefined} startSecs
* @property {string | undefined} endHours
* @property {string | undefined} endMins
* @property {string | undefined} endSecs
*/
/**
@@ -353,23 +356,36 @@ async function setRealTimeMode(page) {
* @param {OffsetValues} offset
* @param {import('@playwright/test').Locator} offsetButton
*/
async function setTimeConductorOffset(page, { hours, mins, secs }, offsetButton) {
await offsetButton.click();
if (hours) {
await page.fill('.pr-time-controls__hrs', hours);
async function setTimeConductorOffset(
page,
{ startHours, startMins, startSecs, endHours, endMins, endSecs }
) {
if (startHours) {
await page.getByRole('spinbutton', { name: 'Start offset hours' }).fill(startHours);
}
if (mins) {
await page.fill('.pr-time-controls__mins', mins);
if (startMins) {
await page.getByRole('spinbutton', { name: 'Start offset minutes' }).fill(startMins);
}
if (secs) {
await page.fill('.pr-time-controls__secs', secs);
if (startSecs) {
await page.getByRole('spinbutton', { name: 'Start offset seconds' }).fill(startSecs);
}
if (endHours) {
await page.getByRole('spinbutton', { name: 'End offset hours' }).fill(endHours);
}
if (endMins) {
await page.getByRole('spinbutton', { name: 'End offset minutes' }).fill(endMins);
}
if (endSecs) {
await page.getByRole('spinbutton', { name: 'End offset seconds' }).fill(endSecs);
}
// Click the check button
await page.locator('.pr-time__buttons .icon-check').click();
await page.locator('.pr-time-input--buttons .icon-check').click();
}
/**
@@ -378,8 +394,9 @@ async function setTimeConductorOffset(page, { hours, mins, secs }, offsetButton)
* @param {OffsetValues} offset
*/
async function setStartOffset(page, offset) {
const startOffsetButton = page.locator('data-testid=conductor-start-offset-button');
await setTimeConductorOffset(page, offset, startOffsetButton);
// Click 'mode' button
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await setTimeConductorOffset(page, offset);
}
/**
@@ -388,8 +405,52 @@ async function setStartOffset(page, offset) {
* @param {OffsetValues} offset
*/
async function setEndOffset(page, offset) {
const endOffsetButton = page.locator('data-testid=conductor-end-offset-button');
await setTimeConductorOffset(page, offset, endOffsetButton);
// Click 'mode' button
await page.getByRole('button', { name: 'Time Conductor Mode', exact: true }).click();
await setTimeConductorOffset(page, offset);
}
async function setTimeConductorBounds(page, startDate, endDate) {
// Bring up the time conductor popup
await page.click('.l-shell__time-conductor.c-compact-tc');
await setTimeBounds(page, startDate, endDate);
await page.keyboard.press('Enter');
}
async function setIndependentTimeConductorBounds(page, startDate, endDate) {
// Activate Independent Time Conductor in Fixed Time Mode
await page.getByRole('switch').click();
// Bring up the time conductor popup
await page.click('.c-conductor-holder--compact .c-compact-tc');
await expect(page.locator('.itc-popout')).toBeVisible();
await setTimeBounds(page, startDate, endDate);
await page.keyboard.press('Enter');
}
async function setTimeBounds(page, startDate, endDate) {
if (startDate) {
// Fill start time
await page
.getByRole('textbox', { name: 'Start date' })
.fill(startDate.toString().substring(0, 10));
await page
.getByRole('textbox', { name: 'Start time' })
.fill(startDate.toString().substring(11, 19));
}
if (endDate) {
// Fill end time
await page.getByRole('textbox', { name: 'End date' }).fill(endDate.toString().substring(0, 10));
await page
.getByRole('textbox', { name: 'End time' })
.fill(endDate.toString().substring(11, 19));
}
}
/**
@@ -503,6 +564,8 @@ module.exports = {
setRealTimeMode,
setStartOffset,
setEndOffset,
setTimeConductorBounds,
setIndependentTimeConductorBounds,
selectInspectorTab,
waitForPlotsToRender
};

View File

@@ -4,19 +4,23 @@
{
"origin": "http://localhost:8080",
"localStorage": [
{
"name": "tcHistory",
"value": "{\"utc\":[{\"start\":1658617494563,\"end\":1658619294563},{\"start\":1658617090044,\"end\":1658618890044},{\"start\":1658616460484,\"end\":1658618260484},{\"start\":1658608882159,\"end\":1658610682159},{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}"
},
{
"name": "mct",
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1658619295366,\"modified\":1658619295366},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702},\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2b6bf89f-877b-42b8-acc1-a9a575efdbe1\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658610682787,\"location\":\"mine\",\"persisted\":1658610682787},\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"b9a9c413-4b94-401d-b0c7-5e404f182616\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618261112,\"location\":\"mine\",\"persisted\":1658618261112},\"3e294eae-6124-409b-a870-554d1bdcdd6f\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"108043b1-9c88-4e1d-8deb-fbf2cdb528f9\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618890910,\"location\":\"mine\",\"persisted\":1658618890910},\"ec24d05d-5df5-4c96-9241-b73636cd19a9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4062bd9b-b788-43dd-ab0a-8fa10a78d4b3\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658619295363,\"location\":\"mine\",\"persisted\":1658619295363}}"
"value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554},\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"73f2d9ae-d1f3-4561-b7fc-ecd5df557249\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1652303755999,\"location\":\"mine\",\"persisted\":1652303756002},\"2d02a680-eb7e-4645-bba2-dd298f76efb8\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4291d80c-303c-4d8d-85e1-10f012b864fb\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1654538965702,\"location\":\"mine\",\"persisted\":1654538965702},\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2b6bf89f-877b-42b8-acc1-a9a575efdbe1\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658610682787,\"location\":\"mine\",\"persisted\":1658610682787},\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"b9a9c413-4b94-401d-b0c7-5e404f182616\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618261112,\"location\":\"mine\",\"persisted\":1658618261112},\"3e294eae-6124-409b-a870-554d1bdcdd6f\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"108043b1-9c88-4e1d-8deb-fbf2cdb528f9\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658618890910,\"location\":\"mine\",\"persisted\":1658618890910},\"ec24d05d-5df5-4c96-9241-b73636cd19a9\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"4062bd9b-b788-43dd-ab0a-8fa10a78d4b3\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1658619295363,\"location\":\"mine\",\"persisted\":1658619295363},\"0ec517e8-6c11-4d98-89b5-c300fe61b304\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550}}"
},
{
"name": "mct-tree-expanded",
"value": "[]"
},
{
"name": "tcHistory",
"value": "{\"utc\":[{\"start\":1658617494563,\"end\":1658619294563},{\"start\":1658617090044,\"end\":1658618890044},{\"start\":1658616460484,\"end\":1658618260484},{\"start\":1658608882159,\"end\":1658610682159},{\"start\":1654537164464,\"end\":1654538964464},{\"start\":1652301954635,\"end\":1652303754635}]}"
},
{
"name": "mct-recent-objects",
"value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"domainObject\":{\"identifier\":{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"},\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"2f1585da-6f7e-4ccd-8a20-590fdf177b5d\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1689710689550,\"location\":\"mine\",\"created\":1689710689550,\"persisted\":1689710689550}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"f64bea3b-58a7-4586-8c05-8b651e5f0bfd\",\"namespace\":\"\"},{\"key\":\"2d02a680-eb7e-4645-bba2-dd298f76efb8\",\"namespace\":\"\"},{\"key\":\"72a5f66b-39a7-4f62-8c40-4a99a33d6a8e\",\"namespace\":\"\"},{\"key\":\"8e4d20f1-9a04-4de5-8db5-c7e08d27f70d\",\"namespace\":\"\"},{\"key\":\"3e294eae-6124-409b-a870-554d1bdcdd6f\",\"namespace\":\"\"},{\"key\":\"ec24d05d-5df5-4c96-9241-b73636cd19a9\",\"namespace\":\"\"},{\"key\":\"0ec517e8-6c11-4d98-89b5-c300fe61b304\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1689710689554,\"modified\":1689710689554}}]"
}
]
}
]
}
}

View File

@@ -41,7 +41,7 @@ test.describe('Form Validation Behavior', () => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
await page.click('button:has-text("Create")');
await page.click(':nth-match(:text("Folder"), 2)');
await page.getByRole('menuitem', { name: ' Folder' }).click();
// Fill in empty string into title and trigger validation with 'Tab'
await page.click('text=Properties Title Notes >> input[type="text"]');

View File

@@ -28,10 +28,10 @@ const { createDomainObjectWithDefaults, createNotification } = require('../../ap
const { test, expect } = require('../../pluginFixtures');
test.describe('Notifications List', () => {
test('Notifications can be dismissed individually', async ({ page }) => {
test.fixme('Notifications can be dismissed individually', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6122'
description: 'https://github.com/nasa/openmct/issues/6820'
});
// Go to baseURL

View File

@@ -98,6 +98,8 @@ test.describe('Time List', () => {
const startBound = testPlan.TEST_GROUP[0].start;
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
await page.goto(timelist.url);
// Switch to fixed time mode with all plan events within the bounds
await page.goto(
`${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view`
@@ -110,7 +112,7 @@ test.describe('Time List', () => {
await test.step('Does not show milliseconds in times', async () => {
// Get the first activity
const row = await page.locator('.js-list-item').first();
const row = page.locator('.js-list-item').first();
// Verify that none fo the times have milliseconds displayed.
// Example: 2024-11-17T16:00:00Z is correct and 2024-11-17T16:00:00.000Z is wrong

View File

@@ -21,7 +21,11 @@
*****************************************************************************/
const { test, expect } = require('../../../pluginFixtures');
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
const {
createDomainObjectWithDefaults,
createPlanFromJSON,
setIndependentTimeConductorBounds
} = require('../../../appActions');
const testPlan = {
TEST_GROUP: [
@@ -78,9 +82,6 @@ test.describe('Time Strip', () => {
});
// Constant locators
const independentTimeConductorInputs = page.locator(
'.l-shell__main-independent-time-conductor .c-input--datetime'
);
const activityBounds = page.locator('.activity-bounds');
// Goto baseURL
@@ -122,9 +123,7 @@ test.describe('Time Strip', () => {
});
await test.step('TimeStrip can use the Independent Time Conductor', async () => {
// Activate Independent Time Conductor in Fixed Time Mode
await page.click('.c-toggle-switch__slider');
expect(await activityBounds.count()).toEqual(0);
expect(await activityBounds.count()).toEqual(5);
// Set the independent time bounds so that only one event is shown
const startBound = testPlan.TEST_GROUP[0].start;
@@ -132,12 +131,7 @@ test.describe('Time Strip', () => {
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await independentTimeConductorInputs.nth(0).fill('');
await independentTimeConductorInputs.nth(0).fill(startBoundString);
await page.keyboard.press('Enter');
await independentTimeConductorInputs.nth(1).fill('');
await independentTimeConductorInputs.nth(1).fill(endBoundString);
await page.keyboard.press('Enter');
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
expect(await activityBounds.count()).toEqual(1);
});
@@ -156,9 +150,6 @@ test.describe('Time Strip', () => {
await page.click("button[title='Save']");
await page.click("li[title='Save and Finish Editing']");
// Activate Independent Time Conductor in Fixed Time Mode
await page.click('.c-toggle-switch__slider');
// All events should be displayed at this point because the
// initial independent context bounds will match the global bounds
expect(await activityBounds.count()).toEqual(5);
@@ -169,12 +160,7 @@ test.describe('Time Strip', () => {
const startBoundString = new Date(startBound).toISOString().replace('T', ' ');
const endBoundString = new Date(endBound).toISOString().replace('T', ' ');
await independentTimeConductorInputs.nth(0).fill('');
await independentTimeConductorInputs.nth(0).fill(startBoundString);
await page.keyboard.press('Enter');
await independentTimeConductorInputs.nth(1).fill('');
await independentTimeConductorInputs.nth(1).fill(endBoundString);
await page.keyboard.press('Enter');
await setIndependentTimeConductorBounds(page, startBoundString, endBoundString);
// Verify that two events are displayed
expect(await activityBounds.count()).toEqual(2);

View File

@@ -41,7 +41,7 @@ test.describe('Clock Generator CRUD Operations', () => {
await page.click('button:has-text("Create")');
// Click Clock
await page.click('text=Clock');
await page.getByRole('menuitem').first().click();
// Click .icon-arrow-down
await page.locator('.icon-arrow-down').click();

View File

@@ -25,7 +25,8 @@ const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode
setRealTimeMode,
setIndependentTimeConductorBounds
} = require('../../../../appActions');
test.describe('Display Layout', () => {
@@ -206,6 +207,56 @@ test.describe('Display Layout', () => {
expect(await page.locator('.l-layout .l-layout__frame').count()).toEqual(0);
});
test('independent time works with display layouts and its children', async ({ page }) => {
await setFixedTimeMode(page);
// Create Example Imagery
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
type: 'Example Imagery'
});
// Create a Display Layout
await createDomainObjectWithDefaults(page, {
type: 'Display Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Display Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(exampleImageryObject.name)
});
let layoutGridHolder = page.locator('.l-layout__grid-holder');
await exampleImageryTreeItem.dragTo(layoutGridHolder);
//adjust so that we can see the independent time conductor toggle
// Adjust object height
await page.locator('div[title="Resize object height"] > input').click();
await page.locator('div[title="Resize object height"] > input').fill('70');
// Adjust object width
await page.locator('div[title="Resize object width"] > input').click();
await page.locator('div[title="Resize object width"] > input').fill('70');
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
const startDate = '2021-12-30 01:01:00.000Z';
const endDate = '2021-12-30 01:11:00.000Z';
await setIndependentTimeConductorBounds(page, startDate, endDate);
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByRole('switch').click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
});
test('When multiple plots are contained in a layout, we only ask for annotations once @couchdb', async ({
page
}) => {

View File

@@ -21,7 +21,10 @@
*****************************************************************************/
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const {
createDomainObjectWithDefaults,
setIndependentTimeConductorBounds
} = require('../../../../appActions');
test.describe('Flexible Layout', () => {
let sineWaveObject;
@@ -158,4 +161,47 @@ test.describe('Flexible Layout', () => {
// Verify that the item has been removed from the layout
expect(await page.locator('.c-fl-container__frame').count()).toEqual(0);
});
test('independent time works with flexible layouts and its children', async ({ page }) => {
// Create Example Imagery
const exampleImageryObject = await createDomainObjectWithDefaults(page, {
type: 'Example Imagery'
});
// Create a Flexible Layout
await createDomainObjectWithDefaults(page, {
type: 'Flexible Layout'
});
// Edit Display Layout
await page.locator('[title="Edit"]').click();
// Expand the 'My Items' folder in the left tree
await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// Add the Sine Wave Generator to the Flexible Layout and save changes
const treePane = page.getByRole('tree', {
name: 'Main Tree'
});
const exampleImageryTreeItem = treePane.getByRole('treeitem', {
name: new RegExp(exampleImageryObject.name)
});
// Add the Sine Wave Generator to the Flexible Layout and save changes
await exampleImageryTreeItem.dragTo(page.locator('.c-fl__container.is-empty').first());
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
// flip on independent time conductor
await setIndependentTimeConductorBounds(
page,
'2021-12-30 01:01:00.000Z',
'2021-12-30 01:11:00.000Z'
);
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByRole('switch').click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
});
});

View File

@@ -27,7 +27,7 @@ but only assume that example imagery is present.
/* globals process */
const { waitForAnimations } = require('../../../../baseFixtures');
const { test, expect } = require('../../../../pluginFixtures');
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const { createDomainObjectWithDefaults, setRealTimeMode } = require('../../../../appActions');
const backgroundImageSelector = '.c-imagery__main-image__background-image';
const panHotkey = process.platform === 'linux' ? ['Shift', 'Alt'] : ['Alt'];
const tagHotkey = ['Shift', 'Alt'];
@@ -46,6 +46,7 @@ test.describe('Example Imagery Object', () => {
// Verify that the created object is focused
await expect(page.locator('.l-browse-bar__object-name')).toContainText(exampleImagery.name);
await page.locator('.c-imagery__main-image__bg').hover({ trial: true });
await page.locator(backgroundImageSelector).waitFor();
});
test('Can use Mouse Wheel to zoom in and out of latest image', async ({ page }) => {
@@ -70,6 +71,68 @@ test.describe('Example Imagery Object', () => {
await dragContrastSliderAndAssertFilterValues(page);
});
test('Can use independent time conductor to change time', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6821'
});
// Test independent fixed time with global fixed time
// flip on independent time conductor
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
await page.getByRole('textbox', { name: 'Start date' }).fill('');
await page.getByRole('textbox', { name: 'Start date' }).fill('2021-12-30');
await page.keyboard.press('Tab');
await page.getByRole('textbox', { name: 'Start time' }).fill('');
await page.getByRole('textbox', { name: 'Start time' }).type('01:01:00');
await page.keyboard.press('Tab');
await page.getByRole('textbox', { name: 'End date' }).fill('');
await page.getByRole('textbox', { name: 'End date' }).type('2021-12-30');
await page.keyboard.press('Tab');
await page.getByRole('textbox', { name: 'End time' }).fill('');
await page.getByRole('textbox', { name: 'End time' }).type('01:11:00');
await page.keyboard.press('Tab');
await page.keyboard.press('Enter');
// expect(await page.getByRole('button', { name: 'Submit time bounds' }).isEnabled()).toBe(true);
// await page.getByRole('button', { name: 'Submit time bounds' }).click();
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
// Test independent fixed time with global realtime
await setRealTimeMode(page);
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
// check image date to be in the past
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// flip it off
await page.getByRole('switch', { name: 'Disable Independent Time Conductor' }).click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
// Test independent realtime with global realtime
await page.getByRole('switch', { name: 'Enable Independent Time Conductor' }).click();
// check image date
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
// change independent time to realtime
await page.getByRole('button', { name: 'Independent Time Conductor Settings' }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
// timestamp shouldn't be in the past anymore
await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
// back to the past
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
await page.getByRole('menuitem', { name: /Real-Time/ }).click();
await page.getByRole('button', { name: 'Independent Time Conductor Mode Menu' }).click();
await page.getByRole('menuitem', { name: /Fixed Timespan/ }).click();
// check image date to be in the past
await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
});
test('Can use alt+drag to move around image once zoomed in', async ({ page }) => {
const deltaYStep = 100; //equivalent to 1x zoom
@@ -189,11 +252,9 @@ test.describe('Example Imagery Object', () => {
test('Using the zoom features does not pause telemetry', async ({ page }) => {
const pausePlayButton = page.locator('.c-button.pause-play');
// open the time conductor drop down
await page.locator('.c-mode-button').click();
// switch to realtime
await setRealTimeMode(page);
// Click local clock
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
// Zoom in via button
@@ -203,7 +264,7 @@ test.describe('Example Imagery Object', () => {
test('Uses low fetch priority', async ({ page }) => {
const priority = await page.locator('.js-imageryView-image').getAttribute('fetchpriority');
await expect(priority).toBe('low');
expect(priority).toBe('low');
});
});
@@ -233,14 +294,11 @@ test.describe('Example Imagery in Display Layout', () => {
description: 'https://github.com/nasa/openmct/issues/3647'
});
// Click time conductor mode button
await page.locator('.c-mode-button').click();
// set realtime mode
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
await setRealTimeMode(page);
// pause/play button
const pausePlayButton = await page.locator('.c-button.pause-play');
const pausePlayButton = page.locator('.c-button.pause-play');
await expect.soft(pausePlayButton).not.toHaveClass(/is-paused/);
@@ -259,14 +317,11 @@ test.describe('Example Imagery in Display Layout', () => {
description: 'https://github.com/nasa/openmct/issues/3647'
});
// Click time conductor mode button
await page.locator('.c-mode-button').click();
// set realtime mode
await page.locator('[data-testid="conductor-modeOption-realtime"]').click();
await setRealTimeMode(page);
// pause/play button
const pausePlayButton = await page.locator('.c-button.pause-play');
const pausePlayButton = page.locator('.c-button.pause-play');
await pausePlayButton.click();
await expect.soft(pausePlayButton).toHaveClass(/is-paused/);
@@ -544,11 +599,8 @@ async function performImageryViewOperationsAndAssert(page) {
const nextImageButton = page.locator('.c-nav--next');
await nextImageButton.click();
// Click time conductor mode button
await page.locator('.c-mode-button').click();
// Select local clock mode
await page.locator('[data-testid=conductor-modeOption-realtime]').click();
// set realtime mode
await setRealTimeMode(page);
// Zoom in on next image
await mouseZoomOnImageAndAssert(page, 2);

View File

@@ -51,10 +51,9 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
page.on('request', (request) => notebookElementsRequests.push(request));
//Clicking Add Page generates
let [notebookUrlRequest, allDocsRequest] = await Promise.all([
let [notebookUrlRequest] = await Promise.all([
// Waits for the next request with the specified url
page.waitForRequest(`**/openmct/${testNotebook.uuid}`),
page.waitForRequest('**/openmct/_all_docs?include_docs=true'),
// Triggers the request
page.click('[aria-label="Add Page"]')
]);
@@ -64,15 +63,13 @@ test.describe('Notebook Tests with CouchDB @couchdb', () => {
// Assert that only two requests are made
// Network Requests are:
// 1) The actual POST to create the page
// 2) The shared worker event from 👆 request
expect(notebookElementsRequests.length).toBe(2);
expect(notebookElementsRequests.length).toBe(1);
// Assert on request object
expect(notebookUrlRequest.postDataJSON().metadata.name).toBe(testNotebook.name);
expect(notebookUrlRequest.postDataJSON().model.persisted).toBeGreaterThanOrEqual(
notebookUrlRequest.postDataJSON().model.modified
);
expect(allDocsRequest.postDataJSON().keys).toContain(testNotebook.uuid);
// Add an entry
// Network Requests are:

View File

@@ -134,7 +134,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
// Click the context menu button for the new page
await page.getByTitle('Open context menu').click();
// Delete the page
await page.getByRole('listitem', { name: 'Delete Page' }).click();
await page.getByRole('menuitem', { name: 'Delete Page' }).click();
// Click OK button
await page.getByRole('button', { name: 'Ok' }).click();

View File

@@ -24,7 +24,7 @@
Testsuite for plot autoscale.
*/
const { selectInspectorTab } = require('../../../../appActions');
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures');
test.use({
viewport: {
@@ -107,7 +107,7 @@ test.describe('Autoscale', () => {
await page.keyboard.up('Alt');
// Ensure the drag worked.
await testYTicks(page, ['0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00', '3.50']);
await testYTicks(page, ['-0.50', '0.00', '0.50', '1.00', '1.50', '2.00', '2.50', '3.00']);
//Wait for canvas to stablize.
await canvas.hover({ trial: true });
@@ -131,12 +131,7 @@ async function setTimeRange(
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click();
await timeInputs.first().fill(start);
await timeInputs.nth(1).click();
await timeInputs.nth(1).fill(end);
await setTimeConductorBounds(page, start, end);
}
/**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -26,7 +26,7 @@ necessarily be used for reference when writing new tests in this area.
*/
const { test, expect } = require('../../../../pluginFixtures');
const { selectInspectorTab } = require('../../../../appActions');
const { selectInspectorTab, setTimeConductorBounds } = require('../../../../appActions');
test.describe('Log plot tests', () => {
test('Log Plot ticks are functionally correct in regular and log mode and after refresh', async ({
@@ -87,12 +87,10 @@ async function makeOverlayPlot(page, myItemsFolderName) {
// Set a specific time range for consistency, otherwise it will change
// on every test to a range based on the current time.
const timeInputs = page.locator('input.c-input--datetime');
await timeInputs.first().click();
await timeInputs.first().fill('2022-03-29 22:00:00.000Z');
const start = '2022-03-29 22:00:00.000Z';
const end = '2022-03-29 22:00:30.000Z';
await timeInputs.nth(1).click();
await timeInputs.nth(1).fill('2022-03-29 22:00:30.000Z');
await setTimeConductorBounds(page, start, end);
// create overlay plot

View File

@@ -32,7 +32,7 @@ const {
waitForPlotsToRender
} = require('../../../../appActions');
test.describe('Plot Tagging', () => {
test.describe.fixme('Plot Tagging', () => {
/**
* Given a canvas and a set of points, tags the points on the canvas.
* @param {import('@playwright/test').Page} page
@@ -167,6 +167,10 @@ test.describe('Plot Tagging', () => {
});
test('Tags work with Overlay Plots', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6822'
});
//Test.slow decorator is currently broken. Needs to be fixed in https://github.com/nasa/openmct/issues/5374
test.slow();

View File

@@ -20,7 +20,10 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const { createDomainObjectWithDefaults } = require('../../../../appActions');
const {
createDomainObjectWithDefaults,
setTimeConductorBounds
} = require('../../../../appActions');
const { test, expect } = require('../../../../pluginFixtures');
test.describe('Telemetry Table', () => {
@@ -51,18 +54,14 @@ test.describe('Telemetry Table', () => {
await expect(tableWrapper).toHaveClass(/is-paused/);
// Subtract 5 minutes from the current end bound datetime and set it
const endTimeInput = page.locator('input[type="text"].c-input--datetime').nth(1);
await endTimeInput.click();
let endDate = await endTimeInput.inputValue();
// Bring up the time conductor popup
let endDate = await page.locator('[aria-label="End bounds"]').textContent();
endDate = new Date(endDate);
endDate.setUTCMinutes(endDate.getUTCMinutes() - 5);
endDate = endDate.toISOString().replace(/T/, ' ');
await endTimeInput.fill('');
await endTimeInput.fill(endDate);
await page.keyboard.press('Enter');
await setTimeConductorBounds(page, undefined, endDate);
await expect(tableWrapper).not.toHaveClass(/is-paused/);

View File

@@ -25,7 +25,8 @@ const {
setFixedTimeMode,
setRealTimeMode,
setStartOffset,
setEndOffset
setEndOffset,
setTimeConductorBounds
} = require('../../../../appActions');
test.describe('Time conductor operations', () => {
@@ -40,38 +41,36 @@ test.describe('Time conductor operations', () => {
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
const startTimeLocator = page.locator('input[type="text"]').first();
const endTimeLocator = page.locator('input[type="text"]').nth(1);
// Click start time
await startTimeLocator.click();
// Click end time
await endTimeLocator.click();
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.fill(startDate.toString());
await setTimeConductorBounds(page, startDate, endDate);
// invalid start date
startDate = year + 1 + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
await endTimeLocator.click();
await setTimeConductorBounds(page, startDate);
const startDateValidityStatus = await startTimeLocator.evaluate((element) =>
// Bring up the time conductor popup
const timeConductorMode = await page.locator('.c-compact-tc');
await timeConductorMode.click();
const startDateLocator = page.locator('input[type="text"]').first();
const endDateLocator = page.locator('input[type="text"]').nth(2);
await endDateLocator.click();
const startDateValidityStatus = await startDateLocator.evaluate((element) =>
element.checkValidity()
);
expect(startDateValidityStatus).not.toBeTruthy();
// fix to valid start date
startDate = year - 1 + startDate.substring(4);
await startTimeLocator.fill(startDate.toString());
await setTimeConductorBounds(page, startDate);
// invalid end date
endDate = year - 2 + endDate.substring(4);
await endTimeLocator.fill(endDate.toString());
await startTimeLocator.click();
await setTimeConductorBounds(page, undefined, endDate);
const endDateValidityStatus = await endTimeLocator.evaluate((element) =>
await startDateLocator.click();
const endDateValidityStatus = await endDateLocator.evaluate((element) =>
element.checkValidity()
);
expect(endDateValidityStatus).not.toBeTruthy();
@@ -83,11 +82,11 @@ test.describe('Time conductor operations', () => {
test.describe('Time conductor input fields real-time mode', () => {
test('validate input fields in real-time mode', async ({ page }) => {
const startOffset = {
secs: '23'
startSecs: '23'
};
const endOffset = {
secs: '31'
endSecs: '31'
};
// Go to baseURL
@@ -100,15 +99,13 @@ test.describe('Time conductor input fields real-time mode', () => {
await setStartOffset(page, startOffset);
// Verify time was updated on time offset button
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
'00:30:23'
);
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
// Set end time offset
await setEndOffset(page, endOffset);
// Verify time was updated on preceding time offset button
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:31');
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:31');
});
/**
@@ -119,12 +116,12 @@ test.describe('Time conductor input fields real-time mode', () => {
page
}) => {
const startOffset = {
mins: '30',
secs: '23'
startMins: '30',
startSecs: '23'
};
const endOffset = {
secs: '01'
endSecs: '01'
};
// Convert offsets to milliseconds
@@ -150,12 +147,10 @@ test.describe('Time conductor input fields real-time mode', () => {
await setRealTimeMode(page);
// Verify updated start time offset persists after mode switch
await expect(page.locator('data-testid=conductor-start-offset-button')).toContainText(
'00:30:23'
);
await expect(page.locator('.c-compact-tc__setting-value.icon-minus')).toContainText('00:30:23');
// Verify updated end time offset persists after mode switch
await expect(page.locator('data-testid=conductor-end-offset-button')).toContainText('00:00:01');
await expect(page.locator('.c-compact-tc__setting-value.icon-plus')).toContainText('00:00:01');
// Verify url parameters persist after mode switch
await page.waitForNavigation({ waitUntil: 'networkidle' });
@@ -203,11 +198,11 @@ test.describe('Time Conductor History', () => {
// with startBound at 2022-01-01 00:00:00.000Z
// and endBound at 2022-01-01 00:00:00.200Z
await page.goto(
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true',
{ waitUntil: 'networkidle' }
'./#/browse/mine?view=grid&tc.mode=fixed&tc.startBound=1640995200000&tc.endBound=1640995200200&tc.timeSystem=utc&hideInspector=true'
);
await page.locator("[aria-label='Time Conductor History']").hover({ trial: true });
await page.locator("[aria-label='Time Conductor History']").click();
await page.getByRole('button', { name: 'Time Conductor Settings' }).click();
await page.getByRole('button', { name: 'Time Conductor History' }).hover({ trial: true });
await page.getByRole('button', { name: 'Time Conductor History' }).click();
// Validate history item format
const historyItem = page.locator('text="2022-01-01 00:00:00 + 200ms"');

View File

@@ -59,53 +59,60 @@ test.describe('Recent Objects', () => {
await page.mouse.move(0, 100);
await page.mouse.up();
});
test('Navigated objects show up in recents, object renames and deletions are reflected', async ({
page
}) => {
// Verify that both created objects appear in the list and are in the correct order
await assertInitialRecentObjectsListState();
// Navigate to the folder by clicking on the main object name in the recent objects list item
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
await page.waitForURL(`**/${folderA.uuid}?*`);
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
// Rename
folderA.name = `${folderA.name}-NEW!`;
await page.locator('.l-browse-bar__object-name').fill('');
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
await page.keyboard.press('Enter');
// Verify rename has been applied in recent objects list item and objects paths
expect(
await page
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.filter({
hasText: folderA.name
})
.count()
).toBeGreaterThan(0);
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
// Delete
await page.click('button[title="Show selected item in tree"]');
// Delete the folder via the left tree pane treeitem context menu
await page
.getByRole('treeitem', { name: new RegExp(folderA.name) })
.locator('a')
.click({
button: 'right'
test.fixme(
'Navigated objects show up in recents, object renames and deletions are reflected',
async ({ page }) => {
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/nasa/openmct/issues/6818'
});
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();
// Verify that the folder and clock are no longer in the recent objects list
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
});
// Verify that both created objects appear in the list and are in the correct order
await assertInitialRecentObjectsListState();
// Navigate to the folder by clicking on the main object name in the recent objects list item
await page.getByRole('listitem', { name: folderA.name }).getByText(folderA.name).click();
await page.waitForURL(`**/${folderA.uuid}?*`);
expect(recentObjectsList.getByRole('listitem').nth(0).getByText(folderA.name)).toBeTruthy();
// Rename
folderA.name = `${folderA.name}-NEW!`;
await page.locator('.l-browse-bar__object-name').fill('');
await page.locator('.l-browse-bar__object-name').fill(folderA.name);
await page.keyboard.press('Enter');
// Verify rename has been applied in recent objects list item and objects paths
expect(
await page
.getByRole('navigation', {
name: clock.name
})
.locator('a')
.filter({
hasText: folderA.name
})
.count()
).toBeGreaterThan(0);
expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeTruthy();
// Delete
await page.click('button[title="Show selected item in tree"]');
// Delete the folder via the left tree pane treeitem context menu
await page
.getByRole('treeitem', { name: new RegExp(folderA.name) })
.locator('a')
.click({
button: 'right'
});
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();
// Verify that the folder and clock are no longer in the recent objects list
await expect(recentObjectsList.getByRole('listitem', { name: folderA.name })).toBeHidden();
await expect(recentObjectsList.getByRole('listitem', { name: clock.name })).toBeHidden();
}
);
test('Clicking on an object in the path of a recent object navigates to the object', async ({
page,
openmctConfig

View File

@@ -77,11 +77,11 @@ test.describe('Grand Search', () => {
// Click [aria-label="OpenMCT Search"] a >> nth=0
await page.locator('[aria-label="Search Result"] >> nth=0').click();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeInViewport();
// Fill [aria-label="OpenMCT Search"] input[type="search"]
await page.locator('[aria-label="OpenMCT Search"] input[type="search"]').fill('foo');
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).toBeHidden();
await expect(page.locator('[aria-label="Search Result"] >> nth=0')).not.toBeInViewport();
// Click text=Snapshot Save and Finish Editing Save and Continue Editing >> button >> nth=1
await page

View File

@@ -52,7 +52,8 @@ test.describe('Visual - addInit', () => {
path: path.join(__dirname, '../../helper', './addInitRestrictedNotebook.js')
});
//Go to baseURL
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
await createDomainObjectWithDefaults(page, { type: CUSTOM_NAME });

View File

@@ -41,7 +41,8 @@ test.describe('Visual - Controlled Clock @localStorage', () => {
test('Overlay Plot Loading Indicator @localStorage', async ({ page, theme }) => {
// Go to baseURL
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
await page.locator('a:has-text("Unnamed Overlay Plot Overlay Plot")').click();
//Ensure that we're on the Unnamed Overlay Plot object

View File

@@ -39,7 +39,8 @@ const { createDomainObjectWithDefaults } = require('../../appActions');
test.describe('Visual - Default', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
});
test.use({
clockOptions: {
@@ -99,6 +100,8 @@ test.describe('Visual - Default', () => {
let endDate = 'xxxx-01-01 02:00:00.000Z';
endDate = year + endDate.substring(4);
await page.getByRole('button', { name: 'Time Conductor Settings' }).click();
await page.locator('input[type="text"]').nth(1).fill(endDate.toString());
await page.locator('input[type="text"]').first().fill(startDate.toString());

View File

@@ -32,7 +32,8 @@ const percySnapshot = require('@percy/playwright');
test.describe('Grand Search', () => {
test.beforeEach(async ({ page, theme }) => {
//Go to baseURL and Hide Tree
await page.goto('./#/browse/mine?hideTree=true', { waitUntil: 'networkidle' });
await page.goto('./#/browse/mine', { waitUntil: 'networkidle' });
await page.getByTitle('Collapse Browse Pane').click();
});
test.use({
clockOptions: {

View File

@@ -62,7 +62,7 @@ export default class SinewaveLimitProvider extends EventEmitter {
const id = this.#getObjectKeyString(domainObject);
if (this.#isRealTime === undefined) {
this.#updateRealTime(this.#openmct.time.clock());
this.#updateRealTime(this.#openmct.time.getMode());
}
this.#handleClockUpdate();
@@ -92,15 +92,15 @@ export default class SinewaveLimitProvider extends EventEmitter {
if (observers && !this.#watchingTheClock) {
this.#watchingTheClock = true;
this.#openmct.time.on('clock', this.#updateRealTime, this);
this.#openmct.time.on('modeChanged', this.#updateRealTime, this);
} else if (!observers && this.#watchingTheClock) {
this.#watchingTheClock = false;
this.#openmct.time.off('clock', this.#updateRealTime, this);
this.#openmct.time.off('modeChanged', this.#updateRealTime, this);
}
}
#updateRealTime(clock) {
this.#isRealTime = clock !== undefined;
#updateRealTime(mode) {
this.#isRealTime = mode !== 'fixed';
if (!this.#isRealTime) {
Object.keys(this.#observingStaleness).forEach((id) => {

View File

@@ -156,9 +156,9 @@ export default function () {
key: 'thumbnail',
...formatThumbnail
});
openmct.telemetry.addProvider(getRealtimeProvider());
openmct.telemetry.addProvider(getHistoricalProvider());
openmct.telemetry.addProvider(getLadProvider());
openmct.telemetry.addProvider(getRealtimeProvider(openmct));
openmct.telemetry.addProvider(getHistoricalProvider(openmct));
openmct.telemetry.addProvider(getLadProvider(openmct));
};
}
@@ -207,14 +207,14 @@ function getImageLoadDelay(domainObject) {
return imageLoadDelay;
}
function getRealtimeProvider() {
function getRealtimeProvider(openmct) {
return {
supportsSubscribe: (domainObject) => domainObject.type === 'example.imagery',
subscribe: (domainObject, callback) => {
const delay = getImageLoadDelay(domainObject);
const interval = setInterval(() => {
const imageSamples = getImageSamples(domainObject.configuration);
const datum = pointForTimestamp(Date.now(), domainObject.name, imageSamples, delay);
const datum = pointForTimestamp(openmct.time.now(), domainObject.name, imageSamples, delay);
callback(datum);
}, delay);
@@ -225,7 +225,7 @@ function getRealtimeProvider() {
};
}
function getHistoricalProvider() {
function getHistoricalProvider(openmct) {
return {
supportsRequest: (domainObject, options) => {
return domainObject.type === 'example.imagery' && options.strategy !== 'latest';
@@ -233,17 +233,12 @@ function getHistoricalProvider() {
request: (domainObject, options) => {
const delay = getImageLoadDelay(domainObject);
let start = options.start;
const end = Math.min(options.end, Date.now());
const end = Math.min(options.end, openmct.time.now());
const data = [];
while (start <= end && data.length < delay) {
data.push(
pointForTimestamp(
start,
domainObject.name,
getImageSamples(domainObject.configuration),
delay
)
);
const imageSamples = getImageSamples(domainObject.configuration);
const generatedDataPoint = pointForTimestamp(start, domainObject.name, imageSamples, delay);
data.push(generatedDataPoint);
start += delay;
}
@@ -252,7 +247,7 @@ function getHistoricalProvider() {
};
}
function getLadProvider() {
function getLadProvider(openmct) {
return {
supportsRequest: (domainObject, options) => {
return domainObject.type === 'example.imagery' && options.strategy === 'latest';
@@ -260,7 +255,7 @@ function getLadProvider() {
request: (domainObject, options) => {
const delay = getImageLoadDelay(domainObject);
const datum = pointForTimestamp(
Date.now(),
openmct.time.now(),
domainObject.name,
getImageSamples(domainObject.configuration),
delay

View File

@@ -24,7 +24,7 @@ function SimpleVuePlugin() {
container.appendChild(vm.$mount().$el);
},
destroy: function (container) {
vm.$destroy();
//vm.$destroy();
}
};
}

View File

@@ -92,7 +92,9 @@
}
</style>
</head>
<body></body>
<body>
<div id="app"></div>
</body>
<script>
const THIRTY_SECONDS = 30 * 1000;
const ONE_MINUTE = THIRTY_SECONDS * 2;

View File

@@ -12,6 +12,8 @@
"@types/eventemitter3": "1.2.0",
"@types/jasmine": "4.3.4",
"@types/lodash": "4.14.192",
"@vue/compat": "^3.1.0",
"@vue/compiler-sfc": "^3.1.0",
"babel-loader": "9.1.0",
"babel-plugin-istanbul": "6.1.1",
"codecov": "3.8.3",
@@ -22,8 +24,8 @@
"d3-scale": "3.3.0",
"d3-selection": "3.0.0",
"eslint": "8.43.0",
"eslint-plugin-compat": "4.1.4",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-compat": "4.1.4",
"eslint-plugin-playwright": "0.12.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-vue": "9.15.0",
@@ -66,11 +68,10 @@
"style-loader": "3.3.3",
"typescript": "5.1.3",
"uuid": "9.0.0",
"vue": "2.6.14",
"vue": "^3.1.0",
"vue-eslint-parser": "9.3.1",
"vue-loader": "15.9.8",
"vue-template-compiler": "2.6.14",
"webpack": "5.88.0",
"vue-loader": "^16.0.0",
"webpack-cli": "5.1.1",
"webpack-dev-server": "4.15.1",
"webpack-merge": "5.9.0"
@@ -97,6 +98,7 @@
"test:e2e:updatesnapshots": "npx playwright test --config=e2e/playwright-ci.config.js --project=chrome --grep @snapshot --update-snapshots",
"test:e2e:visual": "percy exec --config ./e2e/.percy.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",
"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'",

View File

@@ -96,6 +96,7 @@ define([
};
this.destroy = this.destroy.bind(this);
this.defaultClock = 'local';
[
/**
* Tracks current selection state of the application.
@@ -342,7 +343,17 @@ define([
* @param {HTMLElement} [domElement] the DOM element in which to run
* MCT; if undefined, MCT will be run in the body of the document
*/
MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
MCT.prototype.start = function (
domElement = document.body.firstElementChild,
isHeadlessMode = false
) {
// Create element to mount Layout if it doesn't exist
if (domElement === null) {
domElement = document.createElement('div');
document.body.appendChild(domElement);
}
domElement.id = 'openmct-app';
if (this.types.get('layout') === undefined) {
this.install(
this.plugins.DisplayLayout({
@@ -353,6 +364,10 @@ define([
this.element = domElement;
if (!this.time.getClock()) {
this.time.setClock(this.defaultClock);
}
this.router.route(/^\/$/, () => {
this.router.setPath('/browse/');
});
@@ -365,25 +380,30 @@ define([
*/
if (!isHeadlessMode) {
const appLayout = new Vue({
const appLayout = Vue.createApp({
components: {
Layout: Layout.default
},
provide: {
openmct: this
openmct: Vue.markRaw(this)
},
template: '<Layout ref="layout"></Layout>'
});
domElement.appendChild(appLayout.$mount().$el);
const component = appLayout.mount(domElement);
component.$nextTick(() => {
this.layout = component.$refs.layout;
this.app = appLayout;
Browse(this);
window.addEventListener('beforeunload', this.destroy);
this.router.start();
this.emit('start');
});
} else {
window.addEventListener('beforeunload', this.destroy);
this.layout = appLayout.$refs.layout;
Browse(this);
this.router.start();
this.emit('start');
}
window.addEventListener('beforeunload', this.destroy);
this.router.start();
this.emit('start');
};
MCT.prototype.startHeadless = function () {

View File

@@ -21,6 +21,7 @@
*****************************************************************************/
import objectUtils from '../objects/object-utils';
import CompositionProvider from './CompositionProvider';
import { toRaw } from 'vue';
/**
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
@@ -167,7 +168,7 @@ export default class DefaultCompositionProvider extends CompositionProvider {
*/
add(parent, childId) {
if (!this.includes(parent, childId)) {
const composition = structuredClone(parent.composition);
const composition = structuredClone(toRaw(parent.composition));
composition.push(childId);
this.publicAPI.objects.mutate(parent, 'composition', composition);
}

View File

@@ -10,8 +10,7 @@ import TextAreaField from './components/controls/TextAreaField.vue';
import TextField from './components/controls/TextField.vue';
import ToggleSwitchField from './components/controls/ToggleSwitchField.vue';
import Vue from 'vue';
import mount from 'utils/mount';
export const DEFAULT_CONTROLS_MAP = {
autocomplete: AutoCompleteField,
checkbox: CheckBoxField,
@@ -69,31 +68,40 @@ export default class FormControl {
*/
_getControlViewProvider(control) {
const self = this;
let rowComponent;
let _destroy = null;
return {
show(element, model, onChange) {
rowComponent = new Vue({
el: element,
components: {
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
const { vNode, destroy } = mount(
{
el: element,
components: {
FormControlComponent: DEFAULT_CONTROLS_MAP[control]
},
provide: {
openmct: self.openmct
},
data() {
return {
model,
onChange
};
},
template: `<FormControlComponent :model="model" @onChange="onChange"></FormControlComponent>`
},
provide: {
openmct: self.openmct
},
data() {
return {
model,
onChange
};
},
template: `<FormControlComponent :model="model" @onChange="onChange"></FormControlComponent>`
});
{
element,
app: self.openmct.app
}
);
_destroy = destroy;
return rowComponent;
return vNode;
},
destroy() {
rowComponent.$destroy();
if (_destroy) {
_destroy();
}
}
};
}

View File

@@ -23,8 +23,8 @@
import FormController from './FormController';
import FormProperties from './components/FormProperties.vue';
import Vue from 'vue';
import _ from 'lodash';
import mount from 'utils/mount';
export default class FormsAPI {
constructor(openmct) {
@@ -156,25 +156,28 @@ export default class FormsAPI {
formCancel = onFormAction(reject);
});
const vm = new Vue({
components: { FormProperties },
provide: {
openmct: self.openmct
const { destroy } = mount(
{
components: { FormProperties },
provide: {
openmct: self.openmct
},
data() {
return {
formStructure,
onChange: onFormPropertyChange,
onCancel: formCancel,
onSave: formSave
};
},
template:
'<FormProperties :model="formStructure" @onChange="onChange" @onCancel="onCancel" @onSave="onSave"></FormProperties>'
},
data() {
return {
formStructure,
onChange: onFormPropertyChange,
onCancel: formCancel,
onSave: formSave
};
},
template:
'<FormProperties :model="formStructure" @onChange="onChange" @onCancel="onCancel" @onSave="onSave"></FormProperties>'
}).$mount();
const formElement = vm.$el;
element.append(formElement);
{
element,
app: self.openmct.app
}
);
function onFormPropertyChange(data) {
if (onChange) {
@@ -195,8 +198,7 @@ export default class FormsAPI {
function onFormAction(callback) {
return () => {
formElement.remove();
vm.$destroy();
destroy();
if (callback) {
callback(changes);

View File

@@ -141,7 +141,7 @@ export default {
},
methods: {
onChange(data) {
this.$set(this.invalidProperties, data.model.key, data.invalid);
this.invalidProperties[data.model.key] = data.invalid;
this.$emit('onChange', data);
},

View File

@@ -26,9 +26,7 @@
{{ row.name }}
</div>
<div class="c-form-row__state-indicator" :class="reqClass"></div>
<div v-if="row.control" class="c-form-row__controls">
<div ref="rowElement"></div>
</div>
<div v-if="row.control" ref="rowElement" class="c-form-row__controls"></div>
</div>
</template>
@@ -91,7 +89,7 @@ export default {
this.formControl.show(this.$refs.rowElement, this.row, this.onChange);
},
destroyed() {
unmounted() {
const destroy = this.formControl.destroy;
if (destroy) {
destroy();

View File

@@ -166,7 +166,7 @@ export default {
this.options = this.model.options;
}
},
destroyed() {
unmounted() {
document.body.removeEventListener('click', this.handleOutsideClick);
},
methods: {

View File

@@ -23,6 +23,7 @@
import MenuAPI from './MenuAPI';
import Menu from './menu';
import { createOpenMct, createMouseEvent, resetApplicationState } from '../../utils/testing';
import Vue from 'vue';
describe('The Menu API', () => {
let openmct;
@@ -137,14 +138,13 @@ describe('The Menu API', () => {
it('invokes the destroy method when menu is dismissed', (done) => {
menuOptions.onDestroy = done;
menuAPI.showMenu(x, y, actionsArray, menuOptions);
spyOn(menuAPI, '_clearMenuComponent').and.callThrough();
const vueComponent = menuAPI.menuComponent.component;
spyOn(vueComponent, '$destroy');
menuAPI.showMenu(x, y, actionsArray, menuOptions);
document.body.click();
expect(vueComponent.$destroy).toHaveBeenCalled();
expect(menuAPI._clearMenuComponent).toHaveBeenCalled();
});
it('invokes the onDestroy callback if passed in', (done) => {
@@ -185,7 +185,7 @@ describe('The Menu API', () => {
superMenuItem.dispatchEvent(mouseOverEvent);
const itemDescription = document.querySelector('.l-item-description__description');
menuAPI.menuComponent.component.$nextTick(() => {
Vue.nextTick(() => {
expect(menuElement).not.toBeNull();
expect(itemDescription.innerText).toEqual(actionsArray[0].description);

View File

@@ -20,17 +20,16 @@
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-menu" :class="options.menuClass">
<div class="c-menu" :class="options.menuClass" :style="styleObject">
<ul v-if="options.actions.length && options.actions[0].length" role="menu">
<template v-for="(actionGroups, index) in options.actions">
<div :key="index" role="group">
<template v-for="(actionGroups, index) in options.actions" :key="index">
<div role="group">
<li
v-for="action in actionGroups"
:key="action.name"
role="menuitem"
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
:title="action.description"
:data-testid="action.testId || false"
@click="action.onItemClicked"
>
{{ action.name }}
@@ -42,8 +41,8 @@
class="c-menu__section-separator"
></div>
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
</div></template
>
</div>
</template>
</ul>
<ul v-else role="menu">
@@ -53,7 +52,6 @@
role="menuitem"
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
:title="action.description"
:data-testid="action.testId || false"
@click="action.onItemClicked"
>
{{ action.name }}
@@ -64,7 +62,9 @@
</template>
<script>
import popupMenuMixin from '../mixins/popupMenuMixin';
export default {
mixins: [popupMenuMixin],
inject: ['options']
};
</script>

View File

@@ -20,21 +20,20 @@
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-menu" :class="[options.menuClass, 'c-super-menu']">
<div class="c-menu" :class="[options.menuClass, 'c-super-menu']" :style="styleObject">
<ul
v-if="options.actions.length && options.actions[0].length"
role="menu"
class="c-super-menu__menu"
>
<template v-for="(actionGroups, index) in options.actions">
<div :key="index" role="group">
<template v-for="(actionGroups, index) in options.actions" :key="index">
<div role="group">
<li
v-for="action in actionGroups"
:key="action.name"
role="menuitem"
:class="[action.cssClass, action.isDisabled ? 'disabled' : '']"
:title="action.description"
:data-testid="action.testId || false"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
@@ -59,7 +58,6 @@
role="menuitem"
:class="action.cssClass"
:title="action.description"
:data-testid="action.testId || false"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
@mouseleave="toggleItemDescription()"
@@ -80,9 +78,10 @@
</div>
</div>
</template>
<script>
import popupMenuMixin from '../mixins/popupMenuMixin';
export default {
mixins: [popupMenuMixin],
inject: ['options'],
data: function () {
return {

View File

@@ -22,7 +22,8 @@
import EventEmitter from 'EventEmitter';
import MenuComponent from './components/Menu.vue';
import SuperMenuComponent from './components/SuperMenu.vue';
import Vue from 'vue';
import { h } from 'vue';
import mount from 'utils/mount';
export const MENU_PLACEMENT = {
TOP: 'top',
@@ -51,138 +52,68 @@ class Menu extends EventEmitter {
}
dismiss() {
this.emit('destroy');
document.body.removeChild(this.component.$el);
if (this.destroy) {
this.destroy();
this.destroy = null;
}
document.removeEventListener('click', this.dismiss);
this.component.$destroy();
}
show() {
this.component.$mount();
document.body.appendChild(this.component.$el);
let position = this._calculatePopupPosition(this.component.$el);
this.component.$el.style.left = `${position.x}px`;
this.component.$el.style.top = `${position.y}px`;
document.addEventListener('click', this.dismiss);
this.emit('destroy');
}
showMenu() {
this.component = new Vue({
components: {
MenuComponent
if (this.destroy) {
return;
}
const { vNode, destroy } = mount({
render() {
return h(MenuComponent);
},
provide: {
options: this.options
},
template: '<menu-component />'
// TODO: Remove this exception upon full migration to Vue 3
// https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#render-function-argument
compatConfig: {
RENDER_FUNCTION: false
}
});
this.el = vNode.el;
this.destroy = destroy;
this.show();
}
showSuperMenu() {
this.component = new Vue({
components: {
SuperMenuComponent
const { vNode, destroy } = mount({
data() {
return {
top: '0px',
left: '0px'
};
},
render() {
return h(SuperMenuComponent);
},
provide: {
options: this.options
},
template: '<super-menu-component />'
// TODO: Remove this exception upon full migration to Vue 3
// https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#render-function-argument
compatConfig: {
RENDER_FUNCTION: false
}
});
this.el = vNode.el;
this.destroy = destroy;
this.show();
}
/**
* @private
*/
_calculatePopupPosition(menuElement) {
let menuDimensions = menuElement.getBoundingClientRect();
if (!this.options.placement) {
this.options.placement = MENU_PLACEMENT.BOTTOM_RIGHT;
}
const menuPosition = this._getMenuPositionBasedOnPlacement(menuDimensions);
return this._preventMenuOverflow(menuPosition, menuDimensions);
}
/**
* @private
*/
_getMenuPositionBasedOnPlacement(menuDimensions) {
let eventPosX = this.options.x;
let eventPosY = this.options.y;
// Adjust popup menu based on placement
switch (this.options.placement) {
case MENU_PLACEMENT.TOP:
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
eventPosY = this.options.y - menuDimensions.height;
break;
case MENU_PLACEMENT.BOTTOM:
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
break;
case MENU_PLACEMENT.LEFT:
eventPosX = this.options.x - menuDimensions.width;
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
break;
case MENU_PLACEMENT.RIGHT:
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
break;
case MENU_PLACEMENT.TOP_LEFT:
eventPosX = this.options.x - menuDimensions.width;
eventPosY = this.options.y - menuDimensions.height;
break;
case MENU_PLACEMENT.TOP_RIGHT:
eventPosY = this.options.y - menuDimensions.height;
break;
case MENU_PLACEMENT.BOTTOM_LEFT:
eventPosX = this.options.x - menuDimensions.width;
break;
case MENU_PLACEMENT.BOTTOM_RIGHT:
break;
}
return {
x: eventPosX,
y: eventPosY
};
}
/**
* @private
*/
_preventMenuOverflow(menuPosition, menuDimensions) {
let { x: eventPosX, y: eventPosY } = menuPosition;
let overflowX = eventPosX + menuDimensions.width - document.body.clientWidth;
let overflowY = eventPosY + menuDimensions.height - document.body.clientHeight;
if (overflowX > 0) {
eventPosX = eventPosX - overflowX;
}
if (overflowY > 0) {
eventPosY = eventPosY - overflowY;
}
if (eventPosX < 0) {
eventPosX = 0;
}
if (eventPosY < 0) {
eventPosY = 0;
}
return {
x: eventPosX,
y: eventPosY
};
show() {
document.body.appendChild(this.el);
document.addEventListener('click', this.dismiss);
}
}

View File

@@ -0,0 +1,111 @@
import { MENU_PLACEMENT } from '../menu';
export default {
methods: {
/**
* @private
*/
_calculatePopupPosition(menuElement) {
let menuDimensions = menuElement.getBoundingClientRect();
if (!this.options.placement) {
this.options.placement = MENU_PLACEMENT.BOTTOM_RIGHT;
}
const menuPosition = this._getMenuPositionBasedOnPlacement(menuDimensions);
return this._preventMenuOverflow(menuPosition, menuDimensions);
},
/**
* @private
*/
_getMenuPositionBasedOnPlacement(menuDimensions) {
let eventPosX = this.options.x;
let eventPosY = this.options.y;
// Adjust popup menu based on placement
switch (this.options.placement) {
case MENU_PLACEMENT.TOP:
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
eventPosY = this.options.y - menuDimensions.height;
break;
case MENU_PLACEMENT.BOTTOM:
eventPosX = this.options.x - Math.floor(menuDimensions.width / 2);
break;
case MENU_PLACEMENT.LEFT:
eventPosX = this.options.x - menuDimensions.width;
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
break;
case MENU_PLACEMENT.RIGHT:
eventPosY = this.options.y - Math.floor(menuDimensions.height / 2);
break;
case MENU_PLACEMENT.TOP_LEFT:
eventPosX = this.options.x - menuDimensions.width;
eventPosY = this.options.y - menuDimensions.height;
break;
case MENU_PLACEMENT.TOP_RIGHT:
eventPosY = this.options.y - menuDimensions.height;
break;
case MENU_PLACEMENT.BOTTOM_LEFT:
eventPosX = this.options.x - menuDimensions.width;
break;
case MENU_PLACEMENT.BOTTOM_RIGHT:
break;
}
return {
x: eventPosX,
y: eventPosY
};
},
/**
* @private
*/
_preventMenuOverflow(menuPosition, menuDimensions) {
let { x: eventPosX, y: eventPosY } = menuPosition;
let overflowX = eventPosX + menuDimensions.width - document.body.clientWidth;
let overflowY = eventPosY + menuDimensions.height - document.body.clientHeight;
if (overflowX > 0) {
eventPosX = eventPosX - overflowX;
}
if (overflowY > 0) {
eventPosY = eventPosY - overflowY;
}
if (eventPosX < 0) {
eventPosX = 0;
}
if (eventPosY < 0) {
eventPosY = 0;
}
return {
x: eventPosX,
y: eventPosY
};
}
},
mounted() {
this.$nextTick(() => {
const position = this._calculatePopupPosition(this.$el);
this.top = position.y;
this.left = position.x;
});
},
data() {
return {
top: '0px',
left: '0px'
};
},
computed: {
styleObject() {
return {
top: `${this.top}px`,
left: `${this.left}px`
};
}
}
};

View File

@@ -159,6 +159,9 @@ class InMemorySearchProvider {
return pendingQuery.promise;
}
/**
* @private
*/
#localQueryFallBack({ queryId, searchType, query, maxResults }) {
if (searchType === this.searchTypes.OBJECTS) {
return this.localSearchForObjects(queryId, query, maxResults);
@@ -371,7 +374,7 @@ class InMemorySearchProvider {
delete provider.pendingIndex[keyString];
try {
if (domainObject) {
if (domainObject && domainObject.identifier) {
await provider.index(domainObject);
}
} catch (error) {

View File

@@ -96,12 +96,26 @@ class MutableDomainObject {
//Emit events specific to properties affected
let parentPropertiesList = path.split('.');
for (let index = parentPropertiesList.length; index > 0; index--) {
let pathToThisProperty = parentPropertiesList.slice(0, index);
let parentPropertyPath = parentPropertiesList.slice(0, index).join('.');
this._globalEventEmitter.emit(
qualifiedEventName(this, parentPropertyPath),
_.get(this, parentPropertyPath),
_.get(oldModel, parentPropertyPath)
);
const lastPathElement = parentPropertiesList[index - 1];
// Also emit an event for the array whose element has changed so developers do not need to listen to every element of the array.
if (lastPathElement.endsWith(']')) {
const arrayPathElement = lastPathElement.substring(0, lastPathElement.lastIndexOf('['));
pathToThisProperty[index - 1] = arrayPathElement;
const pathToArrayString = pathToThisProperty.join('.');
this._globalEventEmitter.emit(
qualifiedEventName(this, pathToArrayString),
_.get(this, pathToArrayString),
_.get(oldModel, pathToArrayString)
);
}
}
//TODO: Emit events for listeners of child properties when parent changes.

View File

@@ -23,6 +23,9 @@ describe('The Object API', () => {
return USERNAME;
}
});
},
getPossibleRoles() {
return Promise.resolve([]);
}
};
openmct = createOpenMct();

View File

@@ -1,10 +1,10 @@
import DialogComponent from './components/DialogComponent.vue';
import Overlay from './Overlay';
import Vue from 'vue';
import mount from 'utils/mount';
class Dialog extends Overlay {
constructor({ iconClass, message, title, hint, timestamp, ...options }) {
let component = new Vue({
const { vNode, destroy } = mount({
components: {
DialogComponent: DialogComponent
},
@@ -16,17 +16,17 @@ class Dialog extends Overlay {
timestamp
},
template: '<dialog-component></dialog-component>'
}).$mount();
});
super({
element: component.$el,
element: vNode.el,
size: 'fit',
dismissable: false,
...options
});
this.once('destroy', () => {
component.$destroy();
destroy();
});
}
}

View File

@@ -1,6 +1,6 @@
import OverlayComponent from './components/OverlayComponent.vue';
import EventEmitter from 'EventEmitter';
import Vue from 'vue';
import mount from 'utils/mount';
const cssClasses = {
large: 'l-overlay-large',
@@ -28,18 +28,25 @@ class Overlay extends EventEmitter {
this.autoHide = autoHide;
this.dismissable = dismissable !== false;
this.component = new Vue({
components: {
OverlayComponent: OverlayComponent
const { destroy } = mount(
{
components: {
OverlayComponent: OverlayComponent
},
provide: {
dismiss: this.notifyAndDismiss.bind(this),
element,
buttons,
dismissable: this.dismissable
},
template: '<overlay-component></overlay-component>'
},
provide: {
dismiss: this.notifyAndDismiss.bind(this),
element,
buttons,
dismissable: this.dismissable
},
template: '<overlay-component></overlay-component>'
});
{
element: this.container
}
);
this.destroy = destroy;
if (onDestroy) {
this.once('destroy', onDestroy);
@@ -53,7 +60,7 @@ class Overlay extends EventEmitter {
dismiss() {
this.emit('destroy');
document.body.removeChild(this.container);
this.component.$destroy();
this.destroy();
}
//Ensures that any callers are notified that the overlay is dismissed
@@ -67,7 +74,6 @@ class Overlay extends EventEmitter {
**/
show() {
document.body.appendChild(this.container);
this.container.appendChild(this.component.$mount().$el);
}
}

View File

@@ -1,9 +1,8 @@
import ProgressDialogComponent from './components/ProgressDialogComponent.vue';
import Overlay from './Overlay';
import Vue from 'vue';
import mount from 'utils/mount';
let component;
class ProgressDialog extends Overlay {
constructor({
progressPerc,
@@ -15,7 +14,7 @@ class ProgressDialog extends Overlay {
timestamp,
...options
}) {
component = new Vue({
const { vNode, destroy } = mount({
components: {
ProgressDialogComponent: ProgressDialogComponent
},
@@ -35,17 +34,18 @@ class ProgressDialog extends Overlay {
};
},
template: '<progress-dialog-component :model="model"></progress-dialog-component>'
}).$mount();
});
component = vNode.componentInstance;
super({
element: component.$el,
element: vNode.el,
size: 'fit',
dismissable: false,
...options
});
this.once('destroy', () => {
component.$destroy();
destroy();
});
}

View File

@@ -22,7 +22,7 @@
import SelectionComponent from './components/SelectionComponent.vue';
import Overlay from './Overlay';
import Vue from 'vue';
import mount from 'utils/mount';
class Selection extends Overlay {
constructor({
@@ -34,7 +34,7 @@ class Selection extends Overlay {
currentSelection,
...options
}) {
let component = new Vue({
const { vNode, destroy } = mount({
components: {
SelectionComponent: SelectionComponent
},
@@ -47,7 +47,9 @@ class Selection extends Overlay {
currentSelection
},
template: '<selection-component></selection-component>'
}).$mount();
});
const component = vNode.componentInstance;
super({
element: component.$el,
@@ -59,7 +61,7 @@ class Selection extends Overlay {
});
this.once('destroy', () => {
component.$destroy();
destroy();
});
}
}

View File

@@ -27,7 +27,7 @@
v-if="dismissable"
aria-label="Close"
class="c-click-icon c-overlay__close-button icon-x"
@click="destroy"
@click.stop="destroy"
></button>
<div
ref="element"
@@ -71,16 +71,16 @@ export default {
});
},
methods: {
destroy: function () {
destroy() {
if (this.dismissable) {
this.dismiss();
}
},
buttonClickHandler: function (method) {
buttonClickHandler(method) {
method();
this.$emit('destroy');
},
getElementForFocus: function () {
getElementForFocus() {
const defaultElement = this.$refs.element;
if (!this.$refs.buttons) {
return defaultElement;

View File

@@ -204,27 +204,25 @@ export default class TelemetryAPI {
*/
standardizeRequestOptions(options = {}) {
if (!Object.hasOwn(options, 'start')) {
if (options.timeContext?.bounds()) {
options.start = options.timeContext.bounds().start;
const bounds = options.timeContext?.getBounds();
if (bounds?.start) {
options.start = options.timeContext.getBounds().start;
} else {
options.start = this.openmct.time.bounds().start;
options.start = this.openmct.time.getBounds().start;
}
}
if (!Object.hasOwn(options, 'end')) {
if (options.timeContext?.bounds()) {
options.end = options.timeContext.bounds().end;
const bounds = options.timeContext?.getBounds();
if (bounds?.end) {
options.end = options.timeContext.getBounds().end;
} else {
options.end = this.openmct.time.bounds().end;
options.end = this.openmct.time.getBounds().end;
}
}
if (!Object.hasOwn(options, 'domain')) {
options.domain = this.openmct.time.timeSystem().key;
}
if (!Object.hasOwn(options, 'timeContext')) {
options.timeContext = this.openmct.time;
options.domain = this.openmct.time.getTimeSystem().key;
}
return options;

View File

@@ -29,15 +29,20 @@ describe('Telemetry API', () => {
beforeEach(() => {
openmct = {
time: jasmine.createSpyObj('timeAPI', ['timeSystem', 'bounds']),
time: jasmine.createSpyObj('timeAPI', ['timeSystem', 'getTimeSystem', 'bounds', 'getBounds']),
types: jasmine.createSpyObj('typeRegistry', ['get'])
};
openmct.time.timeSystem.and.returnValue({ key: 'system' });
openmct.time.getTimeSystem.and.returnValue({ key: 'system' });
openmct.time.bounds.and.returnValue({
start: 0,
end: 1
});
openmct.time.getBounds.and.returnValue({
start: 0,
end: 1
});
telemetryAPI = new TelemetryAPI(openmct);
});
@@ -261,16 +266,14 @@ describe('Telemetry API', () => {
signal,
start: 0,
end: 1,
domain: 'system',
timeContext: jasmine.any(Object)
domain: 'system'
});
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
signal,
start: 0,
end: 1,
domain: 'system',
timeContext: jasmine.any(Object)
domain: 'system'
});
telemetryProvider.supportsRequest.calls.reset();
@@ -281,16 +284,14 @@ describe('Telemetry API', () => {
signal,
start: 0,
end: 1,
domain: 'system',
timeContext: jasmine.any(Object)
domain: 'system'
});
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
signal,
start: 0,
end: 1,
domain: 'system',
timeContext: jasmine.any(Object)
domain: 'system'
});
});
@@ -309,16 +310,14 @@ describe('Telemetry API', () => {
start: 20,
end: 30,
domain: 'someDomain',
signal,
timeContext: jasmine.any(Object)
signal
});
expect(telemetryProvider.request).toHaveBeenCalledWith(jasmine.any(Object), {
start: 20,
end: 30,
domain: 'someDomain',
signal,
timeContext: jasmine.any(Object)
signal
});
});
});

View File

@@ -23,6 +23,7 @@
import _ from 'lodash';
import EventEmitter from 'EventEmitter';
import { LOADED_ERROR, TIMESYSTEM_KEY_NOTIFICATION, TIMESYSTEM_KEY_WARNING } from './constants';
import { TIME_CONTEXT_EVENTS } from '../time/constants';
/**
* @typedef {import('../objects/ObjectAPI').DomainObject} DomainObject
@@ -60,13 +61,17 @@ export default class TelemetryCollection extends EventEmitter {
this.futureBuffer = [];
this.parseTime = undefined;
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
if (!Object.hasOwn(options, 'timeContext')) {
options.timeContext = this.openmct.time;
}
this.options = options;
this.unsubscribe = undefined;
this.options = this.openmct.telemetry.standardizeRequestOptions(options);
this.pageState = undefined;
this.lastBounds = undefined;
this.requestAbort = undefined;
this.isStrategyLatest = this.options.strategy === 'latest';
this.dataOutsideTimeBounds = false;
this.modeChanged = false;
}
/**
@@ -78,11 +83,11 @@ export default class TelemetryCollection extends EventEmitter {
this._error(LOADED_ERROR);
}
this._setTimeSystem(this.options.timeContext.timeSystem());
this.lastBounds = this.options.timeContext.bounds();
this._setTimeSystem(this.options.timeContext.getTimeSystem());
this.lastBounds = this.options.timeContext.getBounds();
this._watchBounds();
this._watchTimeSystem();
this._watchTimeModeChange();
this._requestHistoricalTelemetry();
this._initiateSubscriptionTelemetry();
@@ -101,6 +106,7 @@ export default class TelemetryCollection extends EventEmitter {
this._unwatchBounds();
this._unwatchTimeSystem();
this._unwatchTimeModeChange();
if (this.unsubscribe) {
this.unsubscribe();
}
@@ -121,7 +127,7 @@ export default class TelemetryCollection extends EventEmitter {
* @private
*/
async _requestHistoricalTelemetry() {
let options = { ...this.options };
let options = this.openmct.telemetry.standardizeRequestOptions({ ...this.options });
const historicalProvider = this.openmct.telemetry.findRequestProvider(
this.domainObject,
options
@@ -301,6 +307,12 @@ export default class TelemetryCollection extends EventEmitter {
* @private
*/
_bounds(bounds, isTick) {
if (this.modeChanged) {
this.modeChanged = false;
this._reset();
return;
}
let startChanged = this.lastBounds.start !== bounds.start;
let endChanged = this.lastBounds.end !== bounds.end;
@@ -433,6 +445,11 @@ export default class TelemetryCollection extends EventEmitter {
this._reset();
}
_timeModeChanged() {
//We're need this so that when the bounds change comes in after this mode change, we can reset and request historic telemetry
this.modeChanged = true;
}
/**
* Reset the telemetry data of the collection, and re-request
* historical telemetry
@@ -450,19 +467,35 @@ export default class TelemetryCollection extends EventEmitter {
}
/**
* adds the _bounds callback to the 'bounds' timeAPI listener
* adds the _bounds callback to the 'boundsChanged' timeAPI listener
* @private
*/
_watchBounds() {
this.options.timeContext.on('bounds', this._bounds, this);
this.options.timeContext.on(TIME_CONTEXT_EVENTS.boundsChanged, this._bounds, this);
}
/**
* removes the _bounds callback from the 'bounds' timeAPI listener
* removes the _bounds callback from the 'boundsChanged' timeAPI listener
* @private
*/
_unwatchBounds() {
this.options.timeContext.off('bounds', this._bounds, this);
this.options.timeContext.off(TIME_CONTEXT_EVENTS.boundsChanged, this._bounds, this);
}
/**
* adds the _timeModeChanged callback to the 'modeChanged' timeAPI listener
* @private
*/
_watchTimeModeChange() {
this.options.timeContext.on(TIME_CONTEXT_EVENTS.modeChanged, this._timeModeChanged, this);
}
/**
* removes the _timeModeChanged callback from the 'modeChanged' timeAPI listener
* @private
*/
_unwatchTimeModeChange() {
this.options.timeContext.off(TIME_CONTEXT_EVENTS.modeChanged, this._timeModeChanged, this);
}
/**
@@ -470,7 +503,11 @@ export default class TelemetryCollection extends EventEmitter {
* @private
*/
_watchTimeSystem() {
this.options.timeContext.on('timeSystem', this._setTimeSystemAndFetchData, this);
this.options.timeContext.on(
TIME_CONTEXT_EVENTS.timeSystemChanged,
this._setTimeSystemAndFetchData,
this
);
}
/**
@@ -478,7 +515,11 @@ export default class TelemetryCollection extends EventEmitter {
* @private
*/
_unwatchTimeSystem() {
this.options.timeContext.off('timeSystem', this._setTimeSystemAndFetchData, this);
this.options.timeContext.off(
TIME_CONTEXT_EVENTS.timeSystemChanged,
this._setTimeSystemAndFetchData,
this
);
}
/**

View File

@@ -20,7 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import TimeContext, { TIME_CONTEXT_EVENTS } from './TimeContext';
import TimeContext from './TimeContext';
import { MODES, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from './constants';
/**
* The IndependentTimeContext handles getting and setting time of the openmct application in general.
@@ -46,7 +47,7 @@ class IndependentTimeContext extends TimeContext {
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
}
bounds(newBounds) {
bounds() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.bounds(...arguments);
} else {
@@ -54,7 +55,23 @@ class IndependentTimeContext extends TimeContext {
}
}
tick(timestamp) {
getBounds() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.getBounds();
} else {
return super.getBounds();
}
}
setBounds() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.setBounds(...arguments);
} else {
return super.setBounds(...arguments);
}
}
tick() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.tick(...arguments);
} else {
@@ -62,7 +79,7 @@ class IndependentTimeContext extends TimeContext {
}
}
clockOffsets(offsets) {
clockOffsets() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.clockOffsets(...arguments);
} else {
@@ -70,11 +87,19 @@ class IndependentTimeContext extends TimeContext {
}
}
stopClock() {
getClockOffsets() {
if (this.upstreamTimeContext) {
this.upstreamTimeContext.stopClock();
return this.upstreamTimeContext.getClockOffsets();
} else {
super.stopClock();
return super.getClockOffsets();
}
}
setClockOffsets() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.setClockOffsets(...arguments);
} else {
return super.setClockOffsets(...arguments);
}
}
@@ -86,10 +111,19 @@ class IndependentTimeContext extends TimeContext {
return this.globalTimeContext.timeSystem(...arguments);
}
/**
* Get the time system of the TimeAPI.
* @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI#
* @method getTimeSystem
*/
getTimeSystem() {
return this.globalTimeContext.getTimeSystem();
}
/**
* Set the active clock. Tick source will be immediately subscribed to
* and ticking will begin. Offsets from 'now' must also be provided. A clock
* can be unset by calling {@link stopClock}.
* and ticking will begin. Offsets from 'now' must also be provided.
*
* @param {Clock || string} keyOrClock The clock to activate, or its key
* @param {ClockOffsets} offsets on each tick these will be used to calculate
@@ -126,15 +160,19 @@ class IndependentTimeContext extends TimeContext {
this.activeClock = clock;
/**
* The active clock has changed. Clock can be unset by calling {@link stopClock}
* The active clock has changed.
* @event clock
* @memberof module:openmct.TimeAPI~
* @property {Clock} clock The newly activated clock, or undefined
* if the system is no longer following a clock source
*/
this.emit('clock', this.activeClock);
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
if (this.activeClock !== undefined) {
//set the mode here or isRealtime will be false even if we're in clock mode
this.setMode(REALTIME_MODE_KEY);
this.clockOffsets(offsets);
this.activeClock.on('tick', this.tick);
}
@@ -145,6 +183,138 @@ class IndependentTimeContext extends TimeContext {
return this.activeClock;
}
/**
* Get the active clock.
* @return {Clock} the currently active clock;
*/
getClock() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.getClock();
}
return this.activeClock;
}
/**
* Set the active clock. Tick source will be immediately subscribed to
* and the currently ticking will begin.
* Offsets from 'now', if provided, will be used to set realtime mode offsets
*
* @param {Clock || string} keyOrClock The clock to activate, or its key
* @fires module:openmct.TimeAPI~clock
* @return {Clock} the currently active clock;
*/
setClock(keyOrClock) {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.setClock(...arguments);
}
let clock;
if (typeof keyOrClock === 'string') {
clock = this.globalTimeContext.clocks.get(keyOrClock);
if (clock === undefined) {
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
}
} else if (typeof keyOrClock === 'object') {
clock = keyOrClock;
if (!this.globalTimeContext.clocks.has(clock.key)) {
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
}
}
const previousClock = this.activeClock;
if (previousClock) {
previousClock.off('tick', this.tick);
}
this.activeClock = clock;
this.activeClock.on('tick', this.tick);
/**
* The active clock has changed.
* @event clock
* @memberof module:openmct.TimeAPI~
* @property {Clock} clock The newly activated clock, or undefined
* if the system is no longer following a clock source
*/
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
return this.activeClock;
}
/**
* Get the current mode.
* @return {Mode} the current mode;
*/
getMode() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.getMode();
}
return this.mode;
}
/**
* Set the mode to either fixed or realtime.
*
* @param {Mode} mode The mode to activate
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
* @fires module:openmct.TimeAPI~clock
* @return {Mode} the currently active mode;
*/
setMode(mode, offsetsOrBounds) {
if (!mode) {
return;
}
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.setMode(...arguments);
}
if (mode === MODES.realtime && this.activeClock === undefined) {
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
}
if (mode !== this.mode) {
this.mode = mode;
/**
* The active mode has changed.
* @event modeChanged
* @memberof module:openmct.TimeAPI~
* @property {Mode} mode The newly activated mode
*/
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
}
//We are also going to set bounds here
if (offsetsOrBounds !== undefined) {
if (this.mode === REALTIME_MODE_KEY) {
this.setClockOffsets(offsetsOrBounds);
} else {
this.setBounds(offsetsOrBounds);
}
}
return this.mode;
}
isRealTime() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.isRealTime(...arguments);
} else {
return super.isRealTime(...arguments);
}
}
now() {
if (this.upstreamTimeContext) {
return this.upstreamTimeContext.now(...arguments);
} else {
return super.now(...arguments);
}
}
/**
* Causes this time context to follow another time context (either the global context, or another upstream time context)
* This allows views to have their own time context which points to the appropriate upstream context as necessary, achieving nesting.
@@ -152,7 +322,7 @@ class IndependentTimeContext extends TimeContext {
followTimeContext() {
this.stopFollowingTimeContext();
if (this.upstreamTimeContext) {
TIME_CONTEXT_EVENTS.forEach((eventName) => {
Object.values(TIME_CONTEXT_EVENTS).forEach((eventName) => {
const thisTimeContext = this;
this.upstreamTimeContext.on(eventName, passthrough);
this.unlisteners.push(() => {
@@ -197,6 +367,7 @@ class IndependentTimeContext extends TimeContext {
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.bounds());
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
}
hasOwnContext() {
@@ -237,6 +408,9 @@ class IndependentTimeContext extends TimeContext {
if (viewKey && key === viewKey) {
//this is necessary as the upstream context gets reassigned after this
this.stopFollowingTimeContext();
if (this.activeClock !== undefined) {
this.activeClock.off('tick', this.tick);
}
let timeContext = this.globalTimeContext;
@@ -259,11 +433,16 @@ class IndependentTimeContext extends TimeContext {
this.followTimeContext();
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.bounds());
this.emit('bounds', this.getBounds());
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.getBounds());
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
this.globalTimeContext.emit('refreshContext', viewKey);
}
}
#copy(object) {
return JSON.parse(JSON.stringify(object));
}
}
export default IndependentTimeContext;

View File

@@ -22,6 +22,7 @@
import GlobalTimeContext from './GlobalTimeContext';
import IndependentTimeContext from '@/api/time/IndependentTimeContext';
import { FIXED_MODE_KEY, REALTIME_MODE_KEY } from '@/api/time/constants';
/**
* The public API for setting and querying the temporal state of the
@@ -134,14 +135,15 @@ class TimeAPI extends GlobalTimeContext {
*/
addIndependentContext(key, value, clockKey) {
let timeContext = this.getIndependentContext(key);
//stop following upstream time context since the view has it's own
timeContext.resetContext();
if (clockKey) {
timeContext.clock(clockKey, value);
timeContext.setClock(clockKey);
timeContext.setMode(REALTIME_MODE_KEY, value);
} else {
timeContext.stopClock();
timeContext.bounds(value);
timeContext.setMode(FIXED_MODE_KEY, value);
}
// Notify any nested views to update, pass in the viewKey so that particular view can skip getting an upstream context
@@ -185,6 +187,7 @@ class TimeAPI extends GlobalTimeContext {
}
let viewTimeContext = this.getIndependentContext(viewKey);
if (!viewTimeContext) {
// If the context doesn't exist yet, create it.
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);

View File

@@ -87,7 +87,7 @@ describe('The Time API', function () {
expect(function () {
api.timeSystem(timeSystem, bounds);
}).not.toThrow();
expect(api.timeSystem()).toBe(timeSystem);
expect(api.timeSystem()).toEqual(timeSystem);
});
it('Disallows setting of time system without bounds', function () {
@@ -110,7 +110,7 @@ describe('The Time API', function () {
expect(function () {
api.timeSystem(timeSystemKey);
}).not.toThrow();
expect(api.timeSystem()).toBe(timeSystem);
expect(api.timeSystem()).toEqual(timeSystem);
});
it('Emits an event when time system changes', function () {
@@ -202,12 +202,12 @@ describe('The Time API', function () {
expect(mockTickSource.off).toHaveBeenCalledWith('tick', jasmine.any(Function));
});
it('Allows the active clock to be set and unset', function () {
xit('Allows the active clock to be set and unset', function () {
expect(api.clock()).toBeUndefined();
api.clock('mts', mockOffsets);
expect(api.clock()).toBeDefined();
api.stopClock();
expect(api.clock()).toBeUndefined();
// api.stopClock();
// expect(api.clock()).toBeUndefined();
});
it('Provides a default time context', () => {

View File

@@ -21,8 +21,7 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
export const TIME_CONTEXT_EVENTS = ['bounds', 'clock', 'timeSystem', 'clockOffsets'];
import { TIME_CONTEXT_EVENTS, MODES, REALTIME_MODE_KEY, FIXED_MODE_KEY } from './constants';
class TimeContext extends EventEmitter {
constructor() {
@@ -42,6 +41,7 @@ class TimeContext extends EventEmitter {
this.activeClock = undefined;
this.offsets = undefined;
this.mode = undefined;
this.tick = this.tick.bind(this);
}
@@ -56,6 +56,8 @@ class TimeContext extends EventEmitter {
* @method timeSystem
*/
timeSystem(timeSystemOrKey, bounds) {
this.#warnMethodDeprecated('"timeSystem"', '"getTimeSystem" and "setTimeSystem"');
if (arguments.length >= 1) {
if (arguments.length === 1 && !this.activeClock) {
throw new Error('Must specify bounds when changing time system without an active clock.');
@@ -91,7 +93,7 @@ class TimeContext extends EventEmitter {
throw 'Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key';
}
this.system = timeSystem;
this.system = this.#copy(timeSystem);
/**
* The time system used by the time
@@ -102,7 +104,10 @@ class TimeContext extends EventEmitter {
* @property {TimeSystem} The value of the currently applied
* Time System
* */
this.emit('timeSystem', this.system);
const system = this.#copy(this.system);
this.emit('timeSystem', system);
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, system);
if (bounds) {
this.bounds(bounds);
}
@@ -163,6 +168,8 @@ class TimeContext extends EventEmitter {
* @method bounds
*/
bounds(newBounds) {
this.#warnMethodDeprecated('"bounds"', '"getBounds" and "setBounds"');
if (arguments.length > 0) {
const validationResult = this.validateBounds(newBounds);
if (validationResult.valid !== true) {
@@ -170,7 +177,7 @@ class TimeContext extends EventEmitter {
}
//Create a copy to avoid direct mutation of conductor bounds
this.boundsVal = JSON.parse(JSON.stringify(newBounds));
this.boundsVal = this.#copy(newBounds);
/**
* The start time, end time, or both have been updated.
* @event bounds
@@ -180,10 +187,11 @@ class TimeContext extends EventEmitter {
* a "tick" event (ie. was an automatic update), false otherwise.
*/
this.emit('bounds', this.boundsVal, false);
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, false);
}
//Return a copy to prevent direct mutation of time conductor bounds.
return JSON.parse(JSON.stringify(this.boundsVal));
return this.#copy(this.boundsVal);
}
/**
@@ -248,6 +256,8 @@ class TimeContext extends EventEmitter {
* @returns {ClockOffsets}
*/
clockOffsets(offsets) {
this.#warnMethodDeprecated('"clockOffsets"', '"getClockOffsets" and "setClockOffsets"');
if (arguments.length > 0) {
const validationResult = this.validateOffsets(offsets);
if (validationResult.valid !== true) {
@@ -278,20 +288,19 @@ class TimeContext extends EventEmitter {
}
/**
* Stop the currently active clock from ticking, and unset it. This will
* Stop following the currently active clock. This will
* revert all views to showing a static time frame defined by the current
* bounds.
*/
stopClock() {
if (this.activeClock) {
this.clock(undefined, undefined);
}
this.#warnMethodDeprecated('"stopClock"');
this.setMode(FIXED_MODE_KEY);
}
/**
* Set the active clock. Tick source will be immediately subscribed to
* and ticking will begin. Offsets from 'now' must also be provided. A clock
* can be unset by calling {@link stopClock}.
* and ticking will begin. Offsets from 'now' must also be provided.
*
* @param {Clock || string} keyOrClock The clock to activate, or its key
* @param {ClockOffsets} offsets on each tick these will be used to calculate
@@ -301,6 +310,8 @@ class TimeContext extends EventEmitter {
* @return {Clock} the currently active clock;
*/
clock(keyOrClock, offsets) {
this.#warnMethodDeprecated('"clock"', '"getClock" and "setClock"');
if (arguments.length === 2) {
let clock;
@@ -324,15 +335,19 @@ class TimeContext extends EventEmitter {
this.activeClock = clock;
/**
* The active clock has changed. Clock can be unset by calling {@link stopClock}
* The active clock has changed.
* @event clock
* @memberof module:openmct.TimeAPI~
* @property {Clock} clock The newly activated clock, or undefined
* if the system is no longer following a clock source
*/
this.emit('clock', this.activeClock);
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
if (this.activeClock !== undefined) {
//set the mode or isRealtime will be false even though we're in clock mode
this.setMode(REALTIME_MODE_KEY);
this.clockOffsets(offsets);
this.activeClock.on('tick', this.tick);
}
@@ -340,7 +355,7 @@ class TimeContext extends EventEmitter {
throw 'When setting the clock, clock offsets must also be provided';
}
return this.activeClock;
return this.isRealTime() ? this.activeClock : undefined;
}
/**
@@ -349,29 +364,304 @@ class TimeContext extends EventEmitter {
* using current offsets.
*/
tick(timestamp) {
if (!this.activeClock) {
return;
// always emit the timestamp
this.emit('tick', timestamp);
if (this.mode === REALTIME_MODE_KEY) {
const newBounds = {
start: timestamp + this.offsets.start,
end: timestamp + this.offsets.end
};
this.boundsVal = newBounds;
// "bounds" will be deprecated in a future release
this.emit('bounds', this.boundsVal, true);
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, true);
}
const newBounds = {
start: timestamp + this.offsets.start,
end: timestamp + this.offsets.end
};
this.boundsVal = newBounds;
this.emit('bounds', this.boundsVal, true);
}
/**
* Checks if this time context is in real-time mode or not.
* Get the timestamp of the current clock
* @returns {number} current timestamp of current clock regardless of mode
* @memberof module:openmct.TimeAPI#
* @method now
*/
now() {
return this.activeClock.currentValue();
}
/**
* Get the time system of the TimeAPI.
* @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI#
* @method getTimeSystem
*/
getTimeSystem() {
return this.system;
}
/**
* Set the time system of the TimeAPI.
* @param {TimeSystem | string} timeSystemOrKey
* @param {module:openmct.TimeAPI~TimeConductorBounds} bounds
* @fires module:openmct.TimeAPI~timeSystem
* @returns {TimeSystem} The currently applied time system
* @memberof module:openmct.TimeAPI#
* @method setTimeSystem
*/
setTimeSystem(timeSystemOrKey, bounds) {
if (timeSystemOrKey === undefined) {
throw 'Please provide a time system';
}
let timeSystem;
if (typeof timeSystemOrKey === 'string') {
timeSystem = this.timeSystems.get(timeSystemOrKey);
if (timeSystem === undefined) {
throw `Unknown time system ${timeSystemOrKey}. Has it been registered with 'addTimeSystem'?`;
}
} else if (typeof timeSystemOrKey === 'object') {
timeSystem = timeSystemOrKey;
if (!this.timeSystems.has(timeSystem.key)) {
throw `Unknown time system ${timeSystemOrKey.key}. Has it been registered with 'addTimeSystem'?`;
}
} else {
throw 'Attempt to set invalid time system in Time API. Please provide a previously registered time system object or key';
}
this.system = this.#copy(timeSystem);
/**
* The time system used by the time
* conductor has changed. A change in Time System will always be
* followed by a bounds event specifying new query bounds.
*
* @event module:openmct.TimeAPI~timeSystem
* @property {TimeSystem} The value of the currently applied
* Time System
* */
this.emit(TIME_CONTEXT_EVENTS.timeSystemChanged, this.#copy(this.system));
this.emit('timeSystem', this.#copy(this.system));
if (bounds) {
this.setBounds(bounds);
}
}
/**
* Get the start and end time of the time conductor. Basic validation
* of bounds is performed.
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
* @memberof module:openmct.TimeAPI#
* @method bounds
*/
getBounds() {
//Return a copy to prevent direct mutation of time conductor bounds.
return this.#copy(this.boundsVal);
}
/**
* Set the start and end time of the time conductor. Basic validation
* of bounds is performed.
*
* @param {module:openmct.TimeAPI~TimeConductorBounds} newBounds
* @throws {Error} Validation error
* @fires module:openmct.TimeAPI~bounds
* @returns {module:openmct.TimeAPI~TimeConductorBounds}
* @memberof module:openmct.TimeAPI#
* @method bounds
*/
setBounds(newBounds) {
const validationResult = this.validateBounds(newBounds);
if (validationResult.valid !== true) {
throw new Error(validationResult.message);
}
//Create a copy to avoid direct mutation of conductor bounds
this.boundsVal = this.#copy(newBounds);
/**
* The start time, end time, or both have been updated.
* @event bounds
* @memberof module:openmct.TimeAPI~
* @property {TimeConductorBounds} bounds The newly updated bounds
* @property {boolean} [tick] `true` if the bounds update was due to
* a "tick" event (i.e. was an automatic update), false otherwise.
*/
this.emit(TIME_CONTEXT_EVENTS.boundsChanged, this.boundsVal, false);
this.emit('bounds', this.boundsVal, false);
}
/**
* Get the active clock.
* @return {Clock} the currently active clock;
*/
getClock() {
return this.activeClock;
}
/**
* Set the active clock. Tick source will be immediately subscribed to
* and the currently ticking will begin.
* Offsets from 'now', if provided, will be used to set realtime mode offsets
*
* @param {Clock || string} keyOrClock The clock to activate, or its key
* @fires module:openmct.TimeAPI~clock
* @return {Clock} the currently active clock;
*/
setClock(keyOrClock) {
let clock;
if (typeof keyOrClock === 'string') {
clock = this.clocks.get(keyOrClock);
if (clock === undefined) {
throw `Unknown clock ${keyOrClock}. Has it been registered with 'addClock'?`;
}
} else if (typeof keyOrClock === 'object') {
clock = keyOrClock;
if (!this.clocks.has(clock.key)) {
throw `Unknown clock ${keyOrClock.key}. Has it been registered with 'addClock'?`;
}
}
const previousClock = this.activeClock;
if (previousClock) {
previousClock.off('tick', this.tick);
}
this.activeClock = clock;
this.activeClock.on('tick', this.tick);
/**
* The active clock has changed.
* @event clock
* @memberof module:openmct.TimeAPI~
* @property {Clock} clock The newly activated clock, or undefined
* if the system is no longer following a clock source
*/
this.emit(TIME_CONTEXT_EVENTS.clockChanged, this.activeClock);
this.emit('clock', this.activeClock);
}
/**
* Get the current mode.
* @return {Mode} the current mode;
*/
getMode() {
return this.mode;
}
/**
* Set the mode to either fixed or realtime.
*
* @param {Mode} mode The mode to activate
* @param {TimeBounds | ClockOffsets} offsetsOrBounds A time window of a fixed width
* @fires module:openmct.TimeAPI~clock
* @return {Mode} the currently active mode;
*/
setMode(mode, offsetsOrBounds) {
if (!mode) {
return;
}
if (mode === MODES.realtime && this.activeClock === undefined) {
throw `Unknown clock. Has a clock been registered with 'addClock'?`;
}
if (mode !== this.mode) {
this.mode = mode;
/**
* The active mode has changed.
* @event modeChanged
* @memberof module:openmct.TimeAPI~
* @property {Mode} mode The newly activated mode
*/
this.emit(TIME_CONTEXT_EVENTS.modeChanged, this.#copy(this.mode));
}
if (offsetsOrBounds !== undefined) {
if (this.isRealTime()) {
this.setClockOffsets(offsetsOrBounds);
} else {
this.setBounds(offsetsOrBounds);
}
}
}
/**
* Checks if this time context is in realtime mode or not.
* @returns {boolean} true if this context is in real-time mode, false if not
*/
isRealTime() {
if (this.clock()) {
return true;
return this.mode === MODES.realtime;
}
/**
* Checks if this time context is in fixed mode or not.
* @returns {boolean} true if this context is in fixed mode, false if not
*/
isFixed() {
return this.mode === MODES.fixed;
}
/**
* Get the currently applied clock offsets.
* @returns {ClockOffsets}
*/
getClockOffsets() {
return this.offsets;
}
/**
* Set the currently applied clock offsets. If no parameter is provided,
* the current value will be returned. If provided, the new value will be
* used as the new clock offsets.
* @param {ClockOffsets} offsets
* @returns {ClockOffsets}
*/
setClockOffsets(offsets) {
const validationResult = this.validateOffsets(offsets);
if (validationResult.valid !== true) {
throw new Error(validationResult.message);
}
return false;
this.offsets = this.#copy(offsets);
const currentValue = this.activeClock.currentValue();
const newBounds = {
start: currentValue + offsets.start,
end: currentValue + offsets.end
};
this.setBounds(newBounds);
/**
* Event that is triggered when clock offsets change.
* @event clockOffsets
* @memberof module:openmct.TimeAPI~
* @property {ClockOffsets} clockOffsets The newly activated clock
* offsets.
*/
this.emit(TIME_CONTEXT_EVENTS.clockOffsetsChanged, this.#copy(offsets));
}
#warnMethodDeprecated(method, newMethod) {
let message = `[DEPRECATION WARNING]: The ${method} API method is deprecated and will be removed in a future version of Open MCT.`;
if (newMethod) {
message += ` Please use the ${newMethod} API method(s) instead.`;
}
// TODO: add docs and point to them in warning.
// For more information and migration instructions, visit [link to documentation or migration guide].
console.warn(message);
}
#copy(object) {
return JSON.parse(JSON.stringify(object));
}
}

22
src/api/time/constants.js Normal file
View File

@@ -0,0 +1,22 @@
export const TIME_CONTEXT_EVENTS = {
//old API events - to be deprecated
bounds: 'bounds',
clock: 'clock',
timeSystem: 'timeSystem',
clockOffsets: 'clockOffsets',
//new API events
tick: 'tick',
modeChanged: 'modeChanged',
boundsChanged: 'boundsChanged',
clockChanged: 'clockChanged',
timeSystemChanged: 'timeSystemChanged',
clockOffsetsChanged: 'clockOffsetsChanged'
};
export const REALTIME_MODE_KEY = 'realtime';
export const FIXED_MODE_KEY = 'fixed';
export const MODES = {
[FIXED_MODE_KEY]: FIXED_MODE_KEY,
[REALTIME_MODE_KEY]: REALTIME_MODE_KEY
};

View File

@@ -22,7 +22,7 @@
import TooltipComponent from './components/TooltipComponent.vue';
import EventEmitter from 'EventEmitter';
import Vue from 'vue';
import mount from 'utils/mount';
class Tooltip extends EventEmitter {
constructor(
@@ -34,9 +34,7 @@ class Tooltip extends EventEmitter {
) {
super();
this.container = document.createElement('div');
this.component = new Vue({
const { vNode, destroy } = mount({
components: {
TooltipComponent: TooltipComponent
},
@@ -48,6 +46,9 @@ class Tooltip extends EventEmitter {
template: '<tooltip-component toolTipText="toolTipText"></tooltip-component>'
});
this.component = vNode.componentInstance;
this._destroy = destroy;
this.isActive = null;
}
@@ -55,8 +56,7 @@ class Tooltip extends EventEmitter {
if (!this.isActive) {
return;
}
document.body.removeChild(this.container);
this.component.$destroy();
this._destroy();
this.isActive = false;
}
@@ -64,8 +64,7 @@ class Tooltip extends EventEmitter {
* @private
**/
show() {
document.body.appendChild(this.container);
this.container.appendChild(this.component.$mount().$el);
document.body.appendChild(this.component.$el);
this.isActive = true;
}
}

View File

@@ -1,5 +1,7 @@
.c-tooltip-wrapper {
max-width: 200px;
height: auto;
width: auto;
padding: $interiorMargin;
}

View File

@@ -32,6 +32,7 @@ describe('The User Status API', () => {
'setPollQuestion',
'getPollQuestion',
'getCurrentUser',
'getPossibleRoles',
'getPossibleStatuses',
'getAllStatusRoles',
'canSetPollQuestion',
@@ -42,6 +43,7 @@ describe('The User Status API', () => {
mockUser = new openmct.user.User('test-user', 'A test user');
userProvider.getCurrentUser.and.returnValue(Promise.resolve(mockUser));
userProvider.getPossibleStatuses.and.returnValue(Promise.resolve([]));
userProvider.getPossibleRoles.and.returnValue(Promise.resolve([]));
userProvider.getAllStatusRoles.and.returnValue(Promise.resolve([]));
userProvider.canSetPollQuestion.and.returnValue(Promise.resolve(false));
userProvider.isLoggedIn.and.returnValue(true);

View File

@@ -21,13 +21,16 @@
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import { markRaw } from 'vue';
export default class LADTableConfiguration extends EventEmitter {
constructor(domainObject, openmct) {
super();
this.domainObject = domainObject;
this.openmct = openmct;
// Prevent Vue from making this a Proxy, otherwise
// it cannot access any private methods (like #mutate()).
this.openmct = markRaw(openmct);
this.objectMutated = this.objectMutated.bind(this);
this.unlistenFromMutation = openmct.objects.observe(

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
import LADTableConfigurationComponent from './components/LADTableConfiguration.vue';
import Vue from 'vue';
import mount from 'utils/mount';
export default function LADTableConfigurationViewProvider(openmct) {
return {
@@ -37,28 +37,34 @@ export default function LADTableConfigurationViewProvider(openmct) {
return object?.type === 'LadTable' || object?.type === 'LadTableSet';
},
view(selection) {
let component;
let _destroy = null;
return {
show(element) {
component = new Vue({
el: element,
components: {
LADTableConfiguration: LADTableConfigurationComponent
const { destroy } = mount(
{
el: element,
components: {
LADTableConfiguration: LADTableConfigurationComponent
},
provide: {
openmct
},
template: '<LADTableConfiguration />'
},
provide: {
openmct
},
template: '<LADTableConfiguration />'
});
{
app: openmct.app,
element
}
);
_destroy = destroy;
},
priority() {
return 1;
},
destroy() {
if (component) {
component.$destroy();
component = undefined;
if (_destroy) {
_destroy();
}
}
};

View File

@@ -22,38 +22,47 @@
import LadTable from './components/LADTable.vue';
import LADTableConfiguration from './LADTableConfiguration';
import Vue from 'vue';
import mount from 'utils/mount';
export default class LADTableView {
constructor(openmct, domainObject, objectPath) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
this.component = undefined;
this.component = null;
this._destroy = null;
}
show(element) {
let ladTableConfiguration = new LADTableConfiguration(this.domainObject, this.openmct);
this.component = new Vue({
el: element,
components: {
LadTable
const { vNode, destroy } = mount(
{
el: element,
components: {
LadTable
},
provide: {
openmct: this.openmct,
currentView: this,
ladTableConfiguration
},
data: () => {
return {
domainObject: this.domainObject,
objectPath: this.objectPath
};
},
template:
'<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
},
provide: {
openmct: this.openmct,
currentView: this,
ladTableConfiguration
},
data: () => {
return {
domainObject: this.domainObject,
objectPath: this.objectPath
};
},
template:
'<lad-table ref="ladTable" :domain-object="domainObject" :object-path="objectPath"></lad-table>'
});
{
app: this.openmct.app,
element
}
);
this.component = vNode.componentInstance;
this._destroy = destroy;
}
getViewContext() {
@@ -64,8 +73,9 @@ export default class LADTableView {
return this.component.$refs.ladTable.getViewContext();
}
destroy(element) {
this.component.$destroy();
this.component = undefined;
destroy() {
if (this._destroy) {
this._destroy();
}
}
}

View File

@@ -22,37 +22,46 @@
import LadTableSet from './components/LadTableSet.vue';
import LADTableConfiguration from './LADTableConfiguration';
import Vue from 'vue';
import mount from 'utils/mount';
export default class LadTableSetView {
constructor(openmct, domainObject, objectPath) {
this.openmct = openmct;
this.domainObject = domainObject;
this.objectPath = objectPath;
this.component = undefined;
this._destroy = null;
this.component = null;
}
show(element) {
let ladTableConfiguration = new LADTableConfiguration(this.domainObject, this.openmct);
this.component = new Vue({
el: element,
components: {
LadTableSet
const { vNode, destroy } = mount(
{
el: element,
components: {
LadTableSet
},
provide: {
openmct: this.openmct,
objectPath: this.objectPath,
currentView: this,
ladTableConfiguration
},
data: () => {
return {
domainObject: this.domainObject
};
},
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
},
provide: {
openmct: this.openmct,
objectPath: this.objectPath,
currentView: this,
ladTableConfiguration
},
data: () => {
return {
domainObject: this.domainObject
};
},
template: '<lad-table-set ref="ladTableSet" :domain-object="domainObject"></lad-table-set>'
});
{
app: this.openmct.app,
element
}
);
this._destroy = destroy;
this.component = vNode.componentInstance;
}
getViewContext() {
@@ -63,8 +72,9 @@ export default class LadTableSetView {
return this.component.$refs.ladTableSet.getViewContext();
}
destroy(element) {
this.component.$destroy();
this.component = undefined;
destroy() {
if (this._destroy) {
this._destroy();
}
}
}

View File

@@ -190,7 +190,7 @@ export default {
this.previewAction = new PreviewAction(this.openmct);
this.previewAction.on('isVisible', this.togglePreviewState);
},
destroyed() {
unmounted() {
this.openmct.time.off('timeSystem', this.updateTimeSystem);
this.telemetryCollection.off('add', this.setLatestValues);
this.telemetryCollection.off('clear', this.resetValues);

View File

@@ -49,7 +49,7 @@
</template>
<script>
import Vue from 'vue';
import Vue, { toRaw } from 'vue';
import LadRow from './LADRow.vue';
import StalenessUtils from '@/utils/staleness';
@@ -139,7 +139,7 @@ export default {
);
this.initializeViewActions();
},
destroyed() {
unmounted() {
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
this.composition.off('add', this.addItem);
@@ -191,7 +191,7 @@ export default {
reorder(reorderPlan) {
const oldItems = this.items.slice();
reorderPlan.forEach((reorderEvent) => {
this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]);
this.items[reorderEvent.newIndex] = oldItems[reorderEvent.oldIndex];
});
},
metadataHasUnits(valueMetadatas) {
@@ -230,7 +230,7 @@ export default {
};
},
toggleFixedLayout() {
const config = structuredClone(this.configuration);
const config = structuredClone(toRaw(this.configuration));
config.isFixedLayout = !this.configuration.isFixedLayout;
this.ladTableConfiguration.updateConfiguration(config);

View File

@@ -87,7 +87,7 @@ export default {
this.composition.load();
},
destroyed() {
unmounted() {
this.ladTableConfiguration.destroy();
this.openmct.editor.off('isEditing', this.toggleEdit);
@@ -126,7 +126,7 @@ export default {
ladTable.domainObject = domainObject;
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
this.$set(this.ladTelemetryObjects, ladTable.key, []);
this.ladTelemetryObjects[ladTable.key] = [];
this.ladTableObjects.push(ladTable);
const composition = this.openmct.composition.get(ladTable.domainObject);
@@ -151,7 +151,7 @@ export default {
);
const ladTable = this.ladTableObjects[index];
this.$delete(this.ladTelemetryObjects, ladTable.key);
delete this.ladTelemetryObjects[ladTable.key];
this.ladTableObjects.splice(index, 1);
this.shouldShowUnitsCheckbox();
@@ -165,7 +165,7 @@ export default {
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
telemetryObjects.push(telemetryObject);
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
this.shouldShowUnitsCheckbox();
};
@@ -179,7 +179,7 @@ export default {
);
telemetryObjects.splice(index, 1);
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
this.shouldShowUnitsCheckbox();
};
@@ -220,11 +220,11 @@ export default {
}
if (showUnitsCheckbox && this.headers.units === undefined) {
this.$set(this.headers, 'units', 'Units');
this.headers.units = 'Units';
}
if (!showUnitsCheckbox && this.headers?.units) {
this.$delete(this.headers, 'units');
delete this.headers.units;
}
},
metadataHasUnits(domainObject) {

View File

@@ -33,8 +33,8 @@
</tr>
</thead>
<tbody>
<template v-for="ladTable in ladTableObjects">
<tr :key="ladTable.key" class="c-table__group-header js-lad-table-set__table-headers">
<template v-for="ladTable in ladTableObjects" :key="ladTable.key">
<tr class="c-table__group-header js-lad-table-set__table-headers">
<td colspan="10">
{{ ladTable.domainObject.name }}
</td>
@@ -74,7 +74,6 @@ export default {
return {
ladTableObjects: [],
ladTelemetryObjects: {},
compositions: [],
viewContext: {},
staleObjects: [],
configuration: this.ladTableConfiguration.getConfiguration()
@@ -115,6 +114,9 @@ export default {
return '';
}
},
created() {
this.compositions = [];
},
mounted() {
this.ladTableConfiguration.on('change', this.handleConfigurationChange);
this.composition = this.openmct.composition.get(this.domainObject);
@@ -125,7 +127,7 @@ export default {
this.stalenessSubscription = {};
},
destroyed() {
unmounted() {
this.ladTableConfiguration.off('change', this.handleConfigurationChange);
this.composition.off('add', this.addLadTable);
this.composition.off('remove', this.removeLadTable);
@@ -147,7 +149,7 @@ export default {
ladTable.key = this.openmct.objects.makeKeyString(domainObject.identifier);
ladTable.objectPath = [domainObject, ...this.objectPath];
this.$set(this.ladTelemetryObjects, ladTable.key, []);
this.ladTelemetryObjects[ladTable.key] = [];
this.ladTableObjects.push(ladTable);
let composition = this.openmct.composition.get(ladTable.domainObject);
@@ -178,7 +180,7 @@ export default {
this.unwatchStaleness(combinedKey);
});
this.$delete(this.ladTelemetryObjects, ladTable.key);
delete this.ladTelemetryObjects[ladTable.key];
this.ladTableObjects.splice(index, 1);
},
reorderLadTables(reorderPlan) {
@@ -201,7 +203,7 @@ export default {
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
telemetryObjects.push(telemetryObject);
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
this.stalenessSubscription[combinedKey] = {};
this.stalenessSubscription[combinedKey].stalenessUtils = new StalenessUtils(
@@ -236,7 +238,7 @@ export default {
this.unwatchStaleness(combinedKey);
telemetryObjects.splice(index, 1);
this.$set(this.ladTelemetryObjects, ladTable.key, telemetryObjects);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
};
},
unwatchStaleness(combinedKey) {

View File

@@ -75,6 +75,7 @@ describe('The LAD Table', () => {
child = document.createElement('div');
parent.appendChild(child);
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
ladPlugin = new LadPlugin();

View File

@@ -20,14 +20,20 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
import { FIXED_MODE_KEY, REALTIME_MODE_KEY, TIME_CONTEXT_EVENTS } from '../../api/time/constants';
const SEARCH_MODE = 'tc.mode';
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
const SEARCH_START_BOUND = 'tc.startBound';
const SEARCH_END_BOUND = 'tc.endBound';
const SEARCH_START_DELTA = 'tc.startDelta';
const SEARCH_END_DELTA = 'tc.endDelta';
const MODE_FIXED = 'fixed';
const TIME_EVENTS = [
TIME_CONTEXT_EVENTS.timeSystemChanged,
TIME_CONTEXT_EVENTS.modeChanged,
TIME_CONTEXT_EVENTS.clockChanged,
TIME_CONTEXT_EVENTS.clockOffsetsChanged
];
export default class URLTimeSettingsSynchronizer {
constructor(openmct) {
@@ -67,7 +73,7 @@ export default class URLTimeSettingsSynchronizer {
}
updateTimeSettings() {
let timeParameters = this.parseParametersFromUrl();
const timeParameters = this.parseParametersFromUrl();
if (this.areTimeParametersValid(timeParameters)) {
this.setTimeApiFromUrl(timeParameters);
@@ -78,21 +84,18 @@ export default class URLTimeSettingsSynchronizer {
}
parseParametersFromUrl() {
let searchParams = this.openmct.router.getAllSearchParams();
let mode = searchParams.get(SEARCH_MODE);
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
let startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
let endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
let bounds = {
const searchParams = this.openmct.router.getAllSearchParams();
const mode = searchParams.get(SEARCH_MODE);
const timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
const startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
const endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
const bounds = {
start: startBound,
end: endBound
};
let startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
let endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
let clockOffsets = {
const startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
const endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
const clockOffsets = {
start: 0 - startOffset,
end: endOffset
};
@@ -106,30 +109,35 @@ export default class URLTimeSettingsSynchronizer {
}
setTimeApiFromUrl(timeParameters) {
if (timeParameters.mode === 'fixed') {
if (this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
this.openmct.time.timeSystem(timeParameters.timeSystem, timeParameters.bounds);
} else if (!this.areStartAndEndEqual(this.openmct.time.bounds(), timeParameters.bounds)) {
this.openmct.time.bounds(timeParameters.bounds);
}
const timeSystem = this.openmct.time.getTimeSystem();
if (this.openmct.time.clock()) {
this.openmct.time.stopClock();
if (timeParameters.mode === FIXED_MODE_KEY) {
// should update timesystem
if (timeSystem.key !== timeParameters.timeSystem) {
this.openmct.time.setTimeSystem(timeParameters.timeSystem, timeParameters.bounds);
}
if (!this.areStartAndEndEqual(this.openmct.time.getBounds(), timeParameters.bounds)) {
this.openmct.time.setMode(FIXED_MODE_KEY, timeParameters.bounds);
} else {
this.openmct.time.setMode(FIXED_MODE_KEY);
}
} else {
if (!this.openmct.time.clock() || this.openmct.time.clock().key !== timeParameters.mode) {
this.openmct.time.clock(timeParameters.mode, timeParameters.clockOffsets);
} else if (
!this.areStartAndEndEqual(this.openmct.time.clockOffsets(), timeParameters.clockOffsets)
) {
this.openmct.time.clockOffsets(timeParameters.clockOffsets);
const clock = this.openmct.time.getClock();
if (clock?.key !== timeParameters.mode) {
this.openmct.time.setClock(timeParameters.mode);
}
if (
!this.openmct.time.timeSystem() ||
this.openmct.time.timeSystem().key !== timeParameters.timeSystem
!this.areStartAndEndEqual(this.openmct.time.getClockOffsets(), timeParameters.clockOffsets)
) {
this.openmct.time.timeSystem(timeParameters.timeSystem);
this.openmct.time.setMode(REALTIME_MODE_KEY, timeParameters.clockOffsets);
} else {
this.openmct.time.setMode(REALTIME_MODE_KEY);
}
if (timeSystem?.key !== timeParameters.timeSystem) {
this.openmct.time.setTimeSystem(timeParameters.timeSystem);
}
}
}
@@ -141,13 +149,14 @@ export default class URLTimeSettingsSynchronizer {
}
setUrlFromTimeApi() {
let searchParams = this.openmct.router.getAllSearchParams();
let clock = this.openmct.time.clock();
let bounds = this.openmct.time.bounds();
let clockOffsets = this.openmct.time.clockOffsets();
const searchParams = this.openmct.router.getAllSearchParams();
const clock = this.openmct.time.getClock();
const mode = this.openmct.time.getMode();
const bounds = this.openmct.time.getBounds();
const clockOffsets = this.openmct.time.getClockOffsets();
if (clock === undefined) {
searchParams.set(SEARCH_MODE, MODE_FIXED);
if (mode === FIXED_MODE_KEY) {
searchParams.set(SEARCH_MODE, FIXED_MODE_KEY);
searchParams.set(SEARCH_START_BOUND, bounds.start);
searchParams.set(SEARCH_END_BOUND, bounds.end);
@@ -168,8 +177,8 @@ export default class URLTimeSettingsSynchronizer {
searchParams.delete(SEARCH_END_BOUND);
}
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
this.openmct.router.setAllSearchParams(searchParams);
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.getTimeSystem().key);
this.openmct.router.updateParams(searchParams);
}
areTimeParametersValid(timeParameters) {
@@ -179,7 +188,7 @@ export default class URLTimeSettingsSynchronizer {
this.isModeValid(timeParameters.mode) &&
this.isTimeSystemValid(timeParameters.timeSystem)
) {
if (timeParameters.mode === 'fixed') {
if (timeParameters.mode === FIXED_MODE_KEY) {
isValid = this.areStartAndEndValid(timeParameters.bounds);
} else {
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
@@ -203,8 +212,9 @@ export default class URLTimeSettingsSynchronizer {
isTimeSystemValid(timeSystem) {
let isValid = timeSystem !== undefined;
if (isValid) {
let timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
const timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
isValid = timeSystemObject !== undefined;
}
@@ -218,18 +228,17 @@ export default class URLTimeSettingsSynchronizer {
isValid = true;
}
if (isValid) {
if (mode.toLowerCase() === MODE_FIXED) {
isValid = true;
} else {
isValid = this.openmct.time.clocks.get(mode) !== undefined;
}
if (
isValid &&
(mode.toLowerCase() === FIXED_MODE_KEY || this.openmct.time.clocks.get(mode) !== undefined)
) {
isValid = true;
}
return isValid;
}
areStartAndEndEqual(firstBounds, secondBounds) {
return firstBounds.start === secondBounds.start && firstBounds.end === secondBounds.end;
return firstBounds?.start === secondBounds.start && firstBounds?.end === secondBounds.end;
}
}

View File

@@ -21,7 +21,7 @@
*****************************************************************************/
import { createOpenMct, resetApplicationState } from 'utils/testing';
describe('The URLTimeSettingsSynchronizer', () => {
xdescribe('The URLTimeSettingsSynchronizer', () => {
let appHolder;
let openmct;
let resolveFunction;
@@ -40,7 +40,6 @@ describe('The URLTimeSettingsSynchronizer', () => {
});
afterEach(() => {
openmct.time.stopClock();
openmct.router.removeListener('change:hash', resolveFunction);
appHolder = undefined;

View File

@@ -171,7 +171,7 @@ xdescribe('AutoflowTabularPlugin', () => {
return [{ hint: hints[0] }];
});
view = provider.view(testObject);
view = provider.view(testObject, [testObject]);
view.show(testContainer);
return Vue.nextTick();

View File

@@ -84,7 +84,7 @@ define([
rowCount: 'reflow'
},
template: autoflowTemplate,
destroyed: function () {
unmounted: function () {
controller.destroy();
if (interval) {
@@ -109,7 +109,7 @@ define([
});
}
AutoflowTabularView.prototype = Object.create(VueView.prototype);
AutoflowTabularView.prototype = Object.create(VueView.default.prototype);
return AutoflowTabularView;
});

View File

@@ -20,15 +20,15 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['vue'], function (Vue) {
function VueView(options) {
const vm = new Vue(options);
import mount from 'utils/mount';
export default function () {
return function VueView(options) {
const { vNode, destroy } = mount(options);
this.show = function (container) {
container.appendChild(vm.$mount().$el);
container.appendChild(vNode.el);
};
this.destroy = vm.$destroy.bind(vm);
}
return VueView;
});
this.destroy = destroy;
};
}

View File

@@ -93,7 +93,7 @@ export default {
});
this.registerListeners();
},
beforeDestroy() {
beforeUnmount() {
if (this.plotResizeObserver) {
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
clearTimeout(this.resizeTimer);

View File

@@ -83,7 +83,7 @@ export default {
this.refreshData
);
},
beforeDestroy() {
beforeUnmount() {
this.stopFollowingTimeContext();
this.removeAllSubscriptions();

View File

@@ -22,7 +22,7 @@
import BarGraphView from './BarGraphView.vue';
import { BAR_GRAPH_KEY, BAR_GRAPH_VIEW } from './BarGraphConstants';
import Vue from 'vue';
import mount from 'utils/mount';
export default function BarGraphViewProvider(openmct) {
function isCompactView(objectPath) {
@@ -44,34 +44,43 @@ export default function BarGraphViewProvider(openmct) {
},
view: function (domainObject, objectPath) {
let component;
let _destroy = null;
let component = null;
return {
show: function (element) {
let isCompact = isCompactView(objectPath);
component = new Vue({
el: element,
components: {
BarGraphView
const { vNode, destroy } = mount(
{
el: element,
components: {
BarGraphView
},
provide: {
openmct,
domainObject,
path: objectPath
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<bar-graph-view ref="graphComponent" :options="options"></bar-graph-view>'
},
provide: {
openmct,
domainObject,
path: objectPath
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<bar-graph-view ref="graphComponent" :options="options"></bar-graph-view>'
});
{
app: openmct.app,
element
}
);
_destroy = destroy;
component = vNode.componentInstance;
},
destroy: function () {
component.$destroy();
component = undefined;
_destroy();
},
onClearData() {
component.$refs.graphComponent.refreshData();

View File

@@ -1,6 +1,6 @@
import { BAR_GRAPH_INSPECTOR_KEY, BAR_GRAPH_KEY } from '../BarGraphConstants';
import Vue from 'vue';
import BarGraphOptions from './BarGraphOptions.vue';
import mount from 'utils/mount';
export default function BarGraphInspectorViewProvider(openmct) {
return {
@@ -16,29 +16,35 @@ export default function BarGraphInspectorViewProvider(openmct) {
return object && object.type === BAR_GRAPH_KEY;
},
view: function (selection) {
let component;
let _destroy = null;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
BarGraphOptions
const { destroy } = mount(
{
el: element,
components: {
BarGraphOptions
},
provide: {
openmct,
domainObject: selection[0][0].context.item
},
template: '<bar-graph-options></bar-graph-options>'
},
provide: {
openmct,
domainObject: selection[0][0].context.item
},
template: '<bar-graph-options></bar-graph-options>'
});
{
app: openmct.app,
element
}
);
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
if (_destroy) {
_destroy();
}
}
};

View File

@@ -167,7 +167,7 @@ export default {
this.registerListeners();
this.composition.load();
},
beforeDestroy() {
beforeUnmount() {
this.openmct.editor.off('isEditing', this.setEditState);
this.stopListening();
},
@@ -192,7 +192,7 @@ export default {
}
},
addSeries(series, index) {
this.$set(this.plotSeries, this.plotSeries.length, series);
this.plotSeries.push(series);
this.setupOptions();
},
removeSeries(seriesIdentifier) {
@@ -200,7 +200,7 @@ export default {
this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier)
);
if (index >= 0) {
this.$delete(this.plotSeries, index);
this.plotSeries.splice(index, 1);
this.setupOptions();
}
},

View File

@@ -115,7 +115,7 @@ export default {
this.initColorAndName
);
},
beforeDestroy() {
beforeUnmount() {
if (this.removeBarStylesListener) {
this.removeBarStylesListener();
}

View File

@@ -23,7 +23,7 @@
import { createOpenMct, resetApplicationState } from 'utils/testing';
import Vue from 'vue';
import BarGraphPlugin from './plugin';
import BarGraph from './BarGraphPlot.vue';
// import BarGraph from './BarGraphPlot.vue';
import EventEmitter from 'EventEmitter';
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './BarGraphConstants';
@@ -125,7 +125,6 @@ describe('the plugin', function () {
describe('The bar graph view', () => {
let barGraphObject;
// eslint-disable-next-line no-unused-vars
let component;
let mockComposition;
beforeEach(async () => {
@@ -153,21 +152,6 @@ describe('the plugin', function () {
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
BarGraph
},
provide: {
openmct: openmct,
domainObject: barGraphObject,
composition: openmct.composition.get(barGraphObject)
},
template: '<BarGraph></BarGraph>'
});
await Vue.nextTick();
});
@@ -179,7 +163,7 @@ describe('the plugin', function () {
expect(plotViewProvider).toBeDefined();
});
it('Renders plotly bar graph', () => {
xit('Renders plotly bar graph', () => {
let barChartElement = element.querySelectorAll('.plotly');
expect(barChartElement.length).toBe(1);
});
@@ -236,10 +220,9 @@ describe('the plugin', function () {
});
});
describe('The spectral plot view for telemetry objects with array values', () => {
xdescribe('The spectral plot view for telemetry objects with array values', () => {
let barGraphObject;
// eslint-disable-next-line no-unused-vars
let component;
let mockComposition;
beforeEach(async () => {
@@ -270,21 +253,6 @@ describe('the plugin', function () {
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
BarGraph
},
provide: {
openmct: openmct,
domainObject: barGraphObject,
composition: openmct.composition.get(barGraphObject)
},
template: '<BarGraph></BarGraph>'
});
await Vue.nextTick();
});

View File

@@ -77,7 +77,7 @@ export default {
this.reloadTelemetry
);
},
beforeDestroy() {
beforeUnmount() {
this.stopFollowingTimeContext();
if (!this.composition) {

View File

@@ -22,7 +22,7 @@
import ScatterPlotView from './ScatterPlotView.vue';
import { SCATTER_PLOT_KEY, SCATTER_PLOT_VIEW, TIME_STRIP_KEY } from './scatterPlotConstants.js';
import Vue from 'vue';
import mount from 'utils/mount';
export default function ScatterPlotViewProvider(openmct) {
function isCompactView(objectPath) {
@@ -44,34 +44,42 @@ export default function ScatterPlotViewProvider(openmct) {
},
view: function (domainObject, objectPath) {
let component;
let _destroy = null;
return {
show: function (element) {
let isCompact = isCompactView(objectPath);
component = new Vue({
el: element,
components: {
ScatterPlotView
const isCompact = isCompactView(objectPath);
const { destroy } = mount(
{
el: element,
components: {
ScatterPlotView
},
provide: {
openmct,
domainObject,
path: objectPath
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<scatter-plot-view :options="options"></scatter-plot-view>'
},
provide: {
openmct,
domainObject,
path: objectPath
},
data() {
return {
options: {
compact: isCompact
}
};
},
template: '<scatter-plot-view :options="options"></scatter-plot-view>'
});
{
app: openmct.app,
element
}
);
_destroy = destroy;
},
destroy: function () {
component.$destroy();
component = undefined;
if (_destroy) {
_destroy();
}
}
};
}

View File

@@ -87,7 +87,10 @@ export default {
watch: {
data: {
immediate: false,
handler: 'updateData'
handler() {
this.updateData();
},
deep: true
}
},
mounted() {
@@ -106,7 +109,7 @@ export default {
this.$refs.plot.on('plotly_relayout', this.zoom);
},
beforeDestroy() {
beforeUnmount() {
if (this.$refs.plot && this.$refs.plot.off) {
this.$refs.plot.off('plotly_relayout', this.zoom);
}

View File

@@ -52,7 +52,7 @@ export default {
mounted() {
this.openmct.editor.on('isEditing', this.setEditState);
},
beforeDestroy() {
beforeUnmount() {
this.openmct.editor.off('isEditing', this.setEditState);
},
methods: {

View File

@@ -64,7 +64,7 @@ export default {
this.registerListeners();
this.composition.load();
},
beforeDestroy() {
beforeUnmount() {
this.stopListening();
},
methods: {
@@ -102,7 +102,7 @@ export default {
}
},
addSeries(series, index) {
this.$set(this.plotSeries, this.plotSeries.length, series);
this.plotSeries.push(series);
this.setAxesLabels();
},
removeSeries(seriesKey) {
@@ -112,7 +112,7 @@ export default {
const foundSeries = seriesIndex > -1;
if (foundSeries) {
this.$delete(this.plotSeries, seriesIndex);
this.plotSeries.splice(seriesIndex, 1);
this.setAxesLabels();
}
},

View File

@@ -89,7 +89,7 @@ export default {
this.registerListeners();
this.composition.load();
},
beforeDestroy() {
beforeUnmount() {
this.stopListening();
},
methods: {
@@ -135,7 +135,7 @@ export default {
}
},
addSeries(series, index) {
this.$set(this.plotSeries, this.plotSeries.length, series);
this.plotSeries.push(series);
this.setupOptions();
},
removeSeries(seriesIdentifier) {
@@ -143,7 +143,7 @@ export default {
this.openmct.objects.areIdsEqual(seriesIdentifier, plotSeries.identifier)
);
if (index >= 0) {
this.$delete(this.plotSeries, index);
this.plotSeries.splice(index, 1);
this.setupOptions();
}
},

View File

@@ -1,5 +1,5 @@
import { SCATTER_PLOT_INSPECTOR_KEY, SCATTER_PLOT_KEY } from '../scatterPlotConstants';
import Vue from 'vue';
import mount from 'utils/mount';
import PlotOptions from './PlotOptions.vue';
export default function ScatterPlotInspectorViewProvider(openmct) {
@@ -16,29 +16,34 @@ export default function ScatterPlotInspectorViewProvider(openmct) {
return object && object.type === SCATTER_PLOT_KEY;
},
view: function (selection) {
let component;
let _destroy = null;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
PlotOptions
const { destroy } = mount(
{
el: element,
components: {
PlotOptions
},
provide: {
openmct,
domainObject: selection[0][0].context.item
},
template: '<plot-options></plot-options>'
},
provide: {
openmct,
domainObject: selection[0][0].context.item
},
template: '<plot-options></plot-options>'
});
{
app: openmct.app,
element
}
);
_destroy = destroy;
},
priority: function () {
return openmct.priority.HIGH + 1;
},
destroy: function () {
if (component) {
component.$destroy();
component = undefined;
if (_destroy) {
_destroy();
}
}
};

View File

@@ -23,8 +23,8 @@ import { SCATTER_PLOT_KEY } from './scatterPlotConstants.js';
import ScatterPlotViewProvider from './ScatterPlotViewProvider';
import ScatterPlotInspectorViewProvider from './inspector/ScatterPlotInspectorViewProvider';
import ScatterPlotCompositionPolicy from './ScatterPlotCompositionPolicy';
import Vue from 'vue';
import ScatterPlotForm from './ScatterPlotForm.vue';
import mount from 'utils/mount';
export default function () {
return function install(openmct) {
@@ -100,24 +100,30 @@ export default function () {
function getScatterPlotFormControl(openmct) {
return {
show(element, model, onChange) {
const rowComponent = new Vue({
el: element,
components: {
ScatterPlotForm
const { vNode } = mount(
{
el: element,
components: {
ScatterPlotForm
},
provide: {
openmct
},
data() {
return {
model,
onChange
};
},
template: `<scatter-plot-form :model="model" @onChange="onChange"></scatter-plot-form>`
},
provide: {
openmct
},
data() {
return {
model,
onChange
};
},
template: `<scatter-plot-form :model="model" @onChange="onChange"></scatter-plot-form>`
});
{
app: openmct.app,
element
}
);
return rowComponent;
return vNode;
}
};
}

View File

@@ -23,7 +23,6 @@
import { createOpenMct, resetApplicationState } from 'utils/testing';
import Vue from 'vue';
import ScatterPlotPlugin from './plugin';
import ScatterPlot from './ScatterPlotView.vue';
import EventEmitter from 'EventEmitter';
import { SCATTER_PLOT_VIEW, SCATTER_PLOT_KEY } from './scatterPlotConstants';
@@ -118,7 +117,6 @@ describe('the plugin', function () {
let testDomainObject;
let scatterPlotObject;
// eslint-disable-next-line no-unused-vars
let component;
let mockComposition;
beforeEach(async () => {
@@ -179,21 +177,6 @@ describe('the plugin', function () {
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
let viewContainer = document.createElement('div');
child.append(viewContainer);
component = new Vue({
el: viewContainer,
components: {
ScatterPlot
},
provide: {
openmct: openmct,
domainObject: scatterPlotObject,
composition: openmct.composition.get(scatterPlotObject)
},
template: '<ScatterPlot></ScatterPlot>'
});
await Vue.nextTick();
});
@@ -205,7 +188,7 @@ describe('the plugin', function () {
expect(plotViewProvider).toBeDefined();
});
it('Renders plotly scatter plot', () => {
xit('Renders plotly scatter plot', () => {
let scatterPlotElement = element.querySelectorAll('.plotly');
expect(scatterPlotElement.length).toBe(1);
});

View File

@@ -19,39 +19,42 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import ClearDataAction from './ClearDataAction';
import GlobalClearIndicator from './components/globalClearIndicator.vue';
import mount from 'utils/mount';
define(['./components/globalClearIndicator.vue', './ClearDataAction', 'vue'], function (
GlobaClearIndicator,
ClearDataAction,
Vue
) {
return function plugin(appliesToObjects, options = { indicator: true }) {
let installIndicator = options.indicator;
export default function plugin(appliesToObjects, options = { indicator: true }) {
let installIndicator = options.indicator;
appliesToObjects = appliesToObjects || [];
appliesToObjects = appliesToObjects || [];
return function install(openmct) {
if (installIndicator) {
let component = new Vue({
return function install(openmct) {
if (installIndicator) {
const { vNode } = mount(
{
components: {
GlobalClearIndicator: GlobaClearIndicator.default
GlobalClearIndicator
},
provide: {
openmct
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
});
},
{
app: openmct.app,
element: document.createElement('div')
}
);
let indicator = {
element: component.$mount().$el,
key: 'global-clear-indicator',
priority: openmct.priority.DEFAULT
};
let indicator = {
element: vNode.el,
key: 'global-clear-indicator',
priority: openmct.priority.DEFAULT
};
openmct.indicators.add(indicator);
}
openmct.indicators.add(indicator);
}
openmct.actions.register(new ClearDataAction.default(openmct, appliesToObjects));
};
openmct.actions.register(new ClearDataAction(openmct, appliesToObjects));
};
});
}

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