diff --git a/API.md b/API.md
index 8f5dc55260d..01ea65aa087 100644
--- a/API.md
+++ b/API.md
@@ -2,7 +2,7 @@
**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`.
diff --git a/e2e/appActions.js b/e2e/appActions.js
index bf637903430..56cc0e5a7f9 100644
--- a/e2e/appActions.js
+++ b/e2e/appActions.js
@@ -314,7 +314,9 @@ async function _isInEditMode(page, identifier) {
*/
async function setTimeConductorMode(page, isFixedTimespan = true) {
// Click 'mode' button
- await page.locator('.c-mode-button').click();
+ const timeConductorMode = await page.locator('.c-compact-tc');
+ await timeConductorMode.click();
+ await timeConductorMode.locator('.js-mode-button').click();
// Switch time conductor mode
if (isFixedTimespan) {
@@ -353,23 +355,23 @@ async function setRealTimeMode(page) {
* @param {OffsetValues} offset
* @param {import('@playwright/test').Locator} offsetButton
*/
-async function setTimeConductorOffset(page, { hours, mins, secs }, offsetButton) {
- await offsetButton.click();
+async function setTimeConductorOffset(page, { hours, mins, secs }) {
+ // await offsetButton.click();
if (hours) {
- await page.fill('.pr-time-controls__hrs', hours);
+ await page.fill('.pr-time-input__hrs', hours);
}
if (mins) {
- await page.fill('.pr-time-controls__mins', mins);
+ await page.fill('.pr-time-input__mins', mins);
}
if (secs) {
- await page.fill('.pr-time-controls__secs', secs);
+ await page.fill('.pr-time-input__secs', secs);
}
// 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 +380,10 @@ 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
+ const timeConductorMode = await page.locator('.c-compact-tc');
+ await timeConductorMode.click();
+ await setTimeConductorOffset(page, offset);
}
/**
@@ -388,8 +392,10 @@ 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
+ const timeConductorMode = await page.locator('.c-compact-tc');
+ await timeConductorMode.click();
+ await setTimeConductorOffset(page, offset);
}
/**
diff --git a/e2e/test-data/VisualTestData_storage.json b/e2e/test-data/VisualTestData_storage.json
index 02fe3cd82b5..017415dce40 100644
--- a/e2e/test-data/VisualTestData_storage.json
+++ b/e2e/test-data/VisualTestData_storage.json
@@ -5,18 +5,18 @@
"origin": "http://localhost:8080",
"localStorage": [
{
- "name": "tcHistory",
- "value": "{\"utc\":[{\"start\":1658617611983,\"end\":1658619411983}]}"
+ "name": "mct",
+ "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1689710399654,\"created\":1689710398656,\"persisted\":1689710399654},\"58f55f3a-46d9-4c37-a726-27b5d38b895a\":{\"identifier\":{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"},\"name\":\"Overlay Plot:b0ba67ab-e383-40c1-a181-82b174e8fdf0\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"}}]},\"notes\":\"framework/generateVisualTestData.e2e.spec.js\\nGenerate Visual Test Data @localStorage\\nchrome\",\"modified\":1689710400878,\"location\":\"mine\",\"created\":1689710399651,\"persisted\":1689710400878},\"19f2e461-190e-4662-8d62-251e90bb7aac\":{\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"identifier\":{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\",\"infinityValues\":false,\"staleness\":false},\"modified\":1689710400433,\"location\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"created\":1689710400433,\"persisted\":1689710400433}}"
},
{
- "name": "mct",
- "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"7fa5749b-8969-494c-9d85-c272516d333c\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1658619412848,\"modified\":1658619412848},\"7fa5749b-8969-494c-9d85-c272516d333c\":{\"identifier\":{\"key\":\"7fa5749b-8969-494c-9d85-c272516d333c\",\"namespace\":\"\"},\"name\":\"Unnamed Overlay Plot\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"67cbb9fc-af46-4148-b9e5-aea11179ae4b\",\"namespace\":\"\"}],\"configuration\":{\"series\":[{\"identifier\":{\"key\":\"67cbb9fc-af46-4148-b9e5-aea11179ae4b\",\"namespace\":\"\"}}]},\"modified\":1658619413566,\"location\":\"mine\",\"persisted\":1658619413567},\"67cbb9fc-af46-4148-b9e5-aea11179ae4b\":{\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"identifier\":{\"key\":\"67cbb9fc-af46-4148-b9e5-aea11179ae4b\",\"namespace\":\"\"},\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\"},\"modified\":1658619413552,\"location\":\"7fa5749b-8969-494c-9d85-c272516d333c\",\"persisted\":1658619413552}}"
+ "name": "mct-recent-objects",
+ "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"},\"name\":\"Overlay Plot:b0ba67ab-e383-40c1-a181-82b174e8fdf0\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateVisualTestData.e2e.spec.js\\nGenerate Visual Test Data @localStorage\\nchrome\",\"modified\":1689710400435,\"location\":\"mine\",\"created\":1689710399651,\"persisted\":1689710400436},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1689710399654,\"created\":1689710398656,\"persisted\":1689710399654},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"domainObject\":{\"identifier\":{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"},\"name\":\"Overlay Plot:b0ba67ab-e383-40c1-a181-82b174e8fdf0\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateVisualTestData.e2e.spec.js\\nGenerate Visual Test Data @localStorage\\nchrome\",\"modified\":1689710400435,\"location\":\"mine\",\"created\":1689710399651,\"persisted\":1689710400436}},{\"objectPath\":[{\"identifier\":{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"},\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\",\"infinityValues\":false,\"staleness\":false},\"modified\":1689710400433,\"location\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"created\":1689710400433,\"persisted\":1689710400433},{\"identifier\":{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"},\"name\":\"Overlay Plot:b0ba67ab-e383-40c1-a181-82b174e8fdf0\",\"type\":\"telemetry.plot.overlay\",\"composition\":[{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"}],\"configuration\":{\"series\":[]},\"notes\":\"framework/generateVisualTestData.e2e.spec.js\\nGenerate Visual Test Data @localStorage\\nchrome\",\"modified\":1689710400435,\"location\":\"mine\",\"created\":1689710399651,\"persisted\":1689710400436},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1689710399654,\"created\":1689710398656,\"persisted\":1689710399654},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/58f55f3a-46d9-4c37-a726-27b5d38b895a/19f2e461-190e-4662-8d62-251e90bb7aac\",\"domainObject\":{\"identifier\":{\"key\":\"19f2e461-190e-4662-8d62-251e90bb7aac\",\"namespace\":\"\"},\"name\":\"Unnamed Sine Wave Generator\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":\"5000\",\"infinityValues\":false,\"staleness\":false},\"modified\":1689710400433,\"location\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"created\":1689710400433,\"persisted\":1689710400433}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1689710399654,\"created\":1689710398656,\"persisted\":1689710399654},{\"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\":\"58f55f3a-46d9-4c37-a726-27b5d38b895a\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1689710399654,\"created\":1689710398656,\"persisted\":1689710399654}}]"
},
{
"name": "mct-tree-expanded",
- "value": "[\"/browse/mine\"]"
+ "value": "[]"
}
]
}
]
-}
+}
\ No newline at end of file
diff --git a/e2e/test-data/recycled_local_storage.json b/e2e/test-data/recycled_local_storage.json
index ab4608e7d81..5af1d40be81 100644
--- a/e2e/test-data/recycled_local_storage.json
+++ b/e2e/test-data/recycled_local_storage.json
@@ -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}}]"
}
]
}
]
-}
+}
\ No newline at end of file
diff --git a/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js b/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js
index 5c41f2d418f..37840b6b62c 100644
--- a/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js
+++ b/e2e/tests/functional/plugins/displayLayout/displayLayout.e2e.spec.js
@@ -206,6 +206,49 @@ 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);
+
+ await page.locator('button[title="Save"]').click();
+ await page.locator('text=Save and Finish Editing').click();
+
+ // flip on independent time conductor
+ await page.getByTitle('Enable independent Time Conductor').first().locator('label').click();
+ await page.getByRole('textbox').nth(1).fill('2021-12-30 01:11:00.000Z');
+ await page.getByRole('textbox').nth(0).fill('2021-12-30 01:01:00.000Z');
+ await page.getByRole('textbox').nth(1).click();
+
+ // check image date
+ await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
+
+ // flip it off
+ await page.getByTitle('Disable independent Time Conductor').first().locator('label').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
}) => {
diff --git a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js
index 940055bed75..ce16227ad0e 100644
--- a/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js
+++ b/e2e/tests/functional/plugins/flexibleLayout/flexibleLayout.e2e.spec.js
@@ -158,4 +158,46 @@ 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 page.getByTitle('Enable independent Time Conductor').first().locator('label').click();
+ await page.getByRole('textbox').nth(1).fill('2021-12-30 01:11:00.000Z');
+ await page.getByRole('textbox').nth(0).fill('2021-12-30 01:01:00.000Z');
+ await page.getByRole('textbox').nth(1).click();
+
+ // check image date
+ await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
+
+ // flip it off
+ await page.getByTitle('Disable independent Time Conductor').first().locator('label').click();
+ // timestamp shouldn't be in the past anymore
+ await expect(page.getByText('2021-12-30 01:11:00.000Z')).toBeHidden();
+ });
});
diff --git a/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js b/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js
index 16792644d61..5ef4a6a06bf 100644
--- a/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js
+++ b/e2e/tests/functional/plugins/imagery/exampleImagery.e2e.spec.js
@@ -70,6 +70,52 @@ test.describe('Example Imagery Object', () => {
await dragContrastSliderAndAssertFilterValues(page);
});
+ test('Can use independent time conductor to change time', async ({ page }) => {
+ // Test independent fixed time with global fixed time
+ // flip on independent time conductor
+ await page.getByTitle('Enable independent Time Conductor').locator('label').click();
+ await page.getByRole('textbox').nth(1).fill('2021-12-30 01:11:00.000Z');
+ await page.getByRole('textbox').nth(0).fill('2021-12-30 01:01:00.000Z');
+ await page.getByRole('textbox').nth(1).click();
+
+ // check image date
+ await expect(page.getByText('2021-12-30 01:11:00.000Z').first()).toBeVisible();
+
+ // flip it off
+ await page.getByTitle('Disable independent Time Conductor').locator('label').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 page.getByRole('button', { name: /Fixed Timespan/ }).click();
+ await page.getByTestId('conductor-modeOption-realtime').click();
+ await page.getByTitle('Enable independent Time Conductor').locator('label').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.getByTitle('Disable independent Time Conductor').locator('label').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.getByTitle('Enable independent Time Conductor').locator('label').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: /Fixed Timespan/ }).click();
+ await page.getByRole('menuitem', { name: /Local Clock/ }).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: /Local Clock/ })
+ .first()
+ .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 +235,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
@@ -233,11 +277,8 @@ 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');
@@ -259,11 +300,8 @@ 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');
@@ -544,11 +582,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);
@@ -893,3 +928,15 @@ async function createImageryView(page) {
page.waitForSelector('.c-message-banner__message')
]);
}
+
+/**
+ * @param {import('@playwright/test').Page} page
+ */
+async function setRealTimeMode(page) {
+ await page.locator('.c-compact-tc').click();
+ await page.waitForSelector('.c-tc-input-popup', { state: 'visible' });
+ // Click mode dropdown
+ await page.getByRole('button', { name: ' Fixed Timespan ' }).click();
+ // Click realtime
+ await page.getByTestId('conductor-modeOption-realtime').click();
+}
diff --git a/example/imagery/plugin.js b/example/imagery/plugin.js
index 573a4a64255..c090c11ea87 100644
--- a/example/imagery/plugin.js
+++ b/example/imagery/plugin.js
@@ -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
diff --git a/src/MCT.js b/src/MCT.js
index a4c43c6701e..50b5c15029a 100644
--- a/src/MCT.js
+++ b/src/MCT.js
@@ -96,6 +96,7 @@ define([
};
this.destroy = this.destroy.bind(this);
+ this.defaultClock = 'local';
[
/**
* Tracks current selection state of the application.
@@ -353,6 +354,10 @@ define([
this.element = domElement;
+ if (!this.time.getClock()) {
+ this.time.setClock(this.defaultClock);
+ }
+
this.router.route(/^\/$/, () => {
this.router.setPath('/browse/');
});
diff --git a/src/api/menu/components/SuperMenu.vue b/src/api/menu/components/SuperMenu.vue
index 91d9970c335..60557cb1f6e 100644
--- a/src/api/menu/components/SuperMenu.vue
+++ b/src/api/menu/components/SuperMenu.vue
@@ -58,7 +58,6 @@
:key="action.name"
role="menuitem"
:class="action.cssClass"
- :title="action.description"
:data-testid="action.testId || false"
@click="action.onItemClicked"
@mouseover="toggleItemDescription(action)"
diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js
index fe15d3e8920..2cb27c6a099 100644
--- a/src/api/telemetry/TelemetryAPI.js
+++ b/src/api/telemetry/TelemetryAPI.js
@@ -204,27 +204,23 @@ export default class TelemetryAPI {
*/
standardizeRequestOptions(options = {}) {
if (!Object.hasOwn(options, 'start')) {
- if (options.timeContext?.bounds()) {
- options.start = options.timeContext.bounds().start;
+ if (options.timeContext?.getBounds()) {
+ 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;
+ if (options.timeContext?.getBounds()) {
+ 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;
diff --git a/src/api/telemetry/TelemetryAPISpec.js b/src/api/telemetry/TelemetryAPISpec.js
index a2550e978fa..66c8f603f3d 100644
--- a/src/api/telemetry/TelemetryAPISpec.js
+++ b/src/api/telemetry/TelemetryAPISpec.js
@@ -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
});
});
});
diff --git a/src/api/telemetry/TelemetryCollection.js b/src/api/telemetry/TelemetryCollection.js
index 97a7dc16212..3b1fd370cec 100644
--- a/src/api/telemetry/TelemetryCollection.js
+++ b/src/api/telemetry/TelemetryCollection.js
@@ -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,8 +61,11 @@ 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;
@@ -78,11 +82,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 +105,7 @@ export default class TelemetryCollection extends EventEmitter {
this._unwatchBounds();
this._unwatchTimeSystem();
+ this._unwatchTimeModeChange();
if (this.unsubscribe) {
this.unsubscribe();
}
@@ -121,7 +126,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
@@ -433,6 +438,10 @@ export default class TelemetryCollection extends EventEmitter {
this._reset();
}
+ _timeModeChanged() {
+ this._reset();
+ }
+
/**
* Reset the telemetry data of the collection, and re-request
* historical telemetry
@@ -450,19 +459,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 +495,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 +507,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
+ );
}
/**
diff --git a/src/api/time/IndependentTimeContext.js b/src/api/time/IndependentTimeContext.js
index c1185742094..dbcec35e23f 100644
--- a/src/api/time/IndependentTimeContext.js
+++ b/src/api/time/IndependentTimeContext.js
@@ -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,122 @@ 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;
+ }
+
/**
* 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 +306,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 +351,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() {
@@ -259,11 +414,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;
diff --git a/src/api/time/TimeAPI.js b/src/api/time/TimeAPI.js
index 617954db3fe..e0e60eaff34 100644
--- a/src/api/time/TimeAPI.js
+++ b/src/api/time/TimeAPI.js
@@ -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);
diff --git a/src/api/time/TimeAPISpec.js b/src/api/time/TimeAPISpec.js
index 97300157e26..5b31e90967f 100644
--- a/src/api/time/TimeAPISpec.js
+++ b/src/api/time/TimeAPISpec.js
@@ -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', () => {
diff --git a/src/api/time/TimeContext.js b/src/api/time/TimeContext.js
index cef987e3da3..8ff1657696f 100644
--- a/src/api/time/TimeContext.js
+++ b/src/api/time/TimeContext.js
@@ -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) {
+ // 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);
+ }
+ }
+
+ /**
+ * 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;
}
- const newBounds = {
- start: timestamp + this.offsets.start,
- end: timestamp + this.offsets.end
- };
+ 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));
+ }
- this.boundsVal = newBounds;
- this.emit('bounds', this.boundsVal, true);
+ if (offsetsOrBounds !== undefined) {
+ if (this.isRealTime()) {
+ this.setClockOffsets(offsetsOrBounds);
+ } else {
+ this.setBounds(offsetsOrBounds);
+ }
+ }
}
/**
- * Checks if this time context is in real-time mode or not.
+ * 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);
+ }
+
+ 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.`;
}
- return false;
+ // 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));
}
}
diff --git a/src/api/time/constants.js b/src/api/time/constants.js
new file mode 100644
index 00000000000..85182a574b7
--- /dev/null
+++ b/src/api/time/constants.js
@@ -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
+};
diff --git a/src/plugins/URLTimeSettingsSynchronizer/URLTimeSettingsSynchronizer.js b/src/plugins/URLTimeSettingsSynchronizer/URLTimeSettingsSynchronizer.js
index 3355dc021af..0dfab710c34 100644
--- a/src/plugins/URLTimeSettingsSynchronizer/URLTimeSettingsSynchronizer.js
+++ b/src/plugins/URLTimeSettingsSynchronizer/URLTimeSettingsSynchronizer.js
@@ -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();
-
- if (clock === undefined) {
- searchParams.set(SEARCH_MODE, MODE_FIXED);
+ 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 (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;
}
}
diff --git a/src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js b/src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
index 1dd6c18b879..67ce2077f03 100644
--- a/src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
+++ b/src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
@@ -40,7 +40,6 @@ describe('The URLTimeSettingsSynchronizer', () => {
});
afterEach(() => {
- openmct.time.stopClock();
openmct.router.removeListener('change:hash', resolveFunction);
appHolder = undefined;
diff --git a/src/plugins/clock/components/Clock.vue b/src/plugins/clock/components/Clock.vue
index bb5456e7603..5e76be63e38 100644
--- a/src/plugins/clock/components/Clock.vue
+++ b/src/plugins/clock/components/Clock.vue
@@ -41,13 +41,13 @@
diff --git a/src/plugins/timeConductor/ConductorHistory.vue b/src/plugins/timeConductor/ConductorHistory.vue
index 3aed6944a9e..64f09f4c081 100644
--- a/src/plugins/timeConductor/ConductorHistory.vue
+++ b/src/plugins/timeConductor/ConductorHistory.vue
@@ -25,6 +25,7 @@