Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nasa/openmct.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Mazzella <ozyx@users.noreply.github.com>2022-09-10 03:31:03 +0300
committerGitHub <noreply@github.com>2022-09-10 03:31:03 +0300
commita7ea5afa59ea56206af61df2747ddb31be85dae2 (patch)
tree109ebaf10ccbf968d8ba4344c0e3f7a8ac4af3fb
parentc231c2d7cbdd9b868fd837c469643791796739b1 (diff)
Fix Independent Time Conductor for Plans within Time Strips (#5671)
* Update `IndependentTimeContext` only if its `objectPath` differs * Ensure independent time conductor, fixed and realtime inputs have the right objectPath * [e2e] Add Plan creation test * [e2e] add Create TimeStrip test * mark new faultManagement suite with @unstable * Upgrade to @playwright/test v1.25.0 * Extract `createPlanFromJSON` to appActions * [e2e] Add TimeStrip test for Plans, Independent Time Contexts * [e2e] Move test annotation to the top * [e2e] fix timestrip test * Update docker image so the tests run * update playwright on GHA as well * [e2e] Fix menu test * Error if no objectPath provided Co-authored-by: Shefali <simplyrender@gmail.com> Co-authored-by: Jesse Mazzella <jesse.d.mazzella@nasa.gov>
-rw-r--r--.circleci/config.yml2
-rw-r--r--.github/workflows/e2e-couchdb.yml2
-rw-r--r--.github/workflows/e2e-pr.yml2
-rw-r--r--e2e/appActions.js56
-rw-r--r--e2e/tests/functional/menu.e2e.spec.js2
-rw-r--r--e2e/tests/functional/planning/plan.e2e.spec.js87
-rw-r--r--e2e/tests/functional/planning/timestrip.e2e.spec.js181
-rw-r--r--e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js22
-rw-r--r--package.json2
-rw-r--r--src/api/time/TimeAPI.js37
-rw-r--r--src/plugins/remoteClock/requestInterceptor.js8
-rw-r--r--src/plugins/timeConductor/ConductorInputsFixed.vue8
-rw-r--r--src/plugins/timeConductor/ConductorInputsRealtime.vue8
-rw-r--r--src/plugins/timeConductor/independent/IndependentTimeConductor.vue8
-rw-r--r--src/ui/components/ObjectView.vue4
15 files changed, 392 insertions, 37 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e9163c13d..45749b0f2 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,7 +2,7 @@ version: 2.1
executors:
pw-focal-development:
docker:
- - image: mcr.microsoft.com/playwright:v1.23.0-focal
+ - image: mcr.microsoft.com/playwright:v1.25.0-focal
environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed
PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps
diff --git a/.github/workflows/e2e-couchdb.yml b/.github/workflows/e2e-couchdb.yml
index 0a7ebe4cc..69c33967a 100644
--- a/.github/workflows/e2e-couchdb.yml
+++ b/.github/workflows/e2e-couchdb.yml
@@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
- - run: npx playwright@1.23.0 install
+ - run: npx playwright@1.25.0 install
- run: npm install
- run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
- run: npm run test:e2e:couchdb
diff --git a/.github/workflows/e2e-pr.yml b/.github/workflows/e2e-pr.yml
index b21bf8ce7..385255797 100644
--- a/.github/workflows/e2e-pr.yml
+++ b/.github/workflows/e2e-pr.yml
@@ -30,7 +30,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: '16'
- - run: npx playwright@1.23.0 install
+ - run: npx playwright@1.25.0 install
- run: npx playwright install chrome-beta
- run: npm install
- run: npm run test:e2e:full
diff --git a/e2e/appActions.js b/e2e/appActions.js
index c7a4428d5..2cfecd1b1 100644
--- a/e2e/appActions.js
+++ b/e2e/appActions.js
@@ -45,6 +45,8 @@
* @property {string} url the relative url to the object (for use with `page.goto()`)
*/
+const Buffer = require('buffer').Buffer;
+
/**
* This common function creates a domain object with the default options. It is the preferred way of creating objects
* in the e2e suite when uninterested in properties of the objects themselves.
@@ -101,6 +103,59 @@ async function createDomainObjectWithDefaults(page, { type, name, parent = 'mine
}
/**
+ * Create a Plan object from JSON with the provided options.
+ * @param {import('@playwright/test').Page} page
+ * @param {*} options
+ * @returns {Promise<CreatedObjectInfo>} An object containing information about the newly created domain object.
+ */
+async function createPlanFromJSON(page, { name, json, parent = 'mine' }) {
+ const parentUrl = await getHashUrlToDomainObject(page, parent);
+
+ // Navigate to the parent object. This is necessary to create the object
+ // in the correct location, such as a folder, layout, or plot.
+ await page.goto(`${parentUrl}?hideTree=true`);
+
+ //Click the Create button
+ await page.click('button:has-text("Create")');
+
+ // Click 'Plan' menu option
+ await page.click(`li:text("Plan")`);
+
+ // Modify the name input field of the domain object to accept 'name'
+ if (name) {
+ const nameInput = page.locator('form[name="mctForm"] .first input[type="text"]');
+ await nameInput.fill("");
+ await nameInput.fill(name);
+ }
+
+ // Upload buffer from memory
+ await page.locator('input#fileElem').setInputFiles({
+ name: 'plan.txt',
+ mimeType: 'text/plain',
+ buffer: Buffer.from(JSON.stringify(json))
+ });
+
+ // Click OK button and wait for Navigate event
+ await Promise.all([
+ page.waitForLoadState(),
+ page.click('[aria-label="Save"]'),
+ // Wait for Save Banner to appear
+ page.waitForSelector('.c-message-banner__message')
+ ]);
+
+ // Wait until the URL is updated
+ await page.waitForURL(`**/mine/*`);
+ const uuid = await getFocusedObjectUuid(page);
+ const objectUrl = await getHashUrlToDomainObject(page, uuid);
+
+ return {
+ uuid,
+ name,
+ url: objectUrl
+ };
+}
+
+/**
* Open the given `domainObject`'s context menu from the object tree.
* Expands the path to the object and scrolls to it if necessary.
*
@@ -258,6 +313,7 @@ async function setEndOffset(page, offset) {
// eslint-disable-next-line no-undef
module.exports = {
createDomainObjectWithDefaults,
+ createPlanFromJSON,
openObjectTreeContextMenu,
getHashUrlToDomainObject,
getFocusedObjectUuid,
diff --git a/e2e/tests/functional/menu.e2e.spec.js b/e2e/tests/functional/menu.e2e.spec.js
index 9dca46cba..9a1e600dd 100644
--- a/e2e/tests/functional/menu.e2e.spec.js
+++ b/e2e/tests/functional/menu.e2e.spec.js
@@ -42,7 +42,7 @@ test.describe('Persistence operations @addInit', () => {
button: 'right'
});
- const menuOptions = page.locator('.c-menu ul');
+ const menuOptions = page.locator('.c-menu li');
await expect.soft(menuOptions).toContainText(['Open In New Tab', 'View', 'Create Link']);
await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);
diff --git a/e2e/tests/functional/planning/plan.e2e.spec.js b/e2e/tests/functional/planning/plan.e2e.spec.js
new file mode 100644
index 000000000..c8b7ece8d
--- /dev/null
+++ b/e2e/tests/functional/planning/plan.e2e.spec.js
@@ -0,0 +1,87 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2022, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+const { test, expect } = require('../../../pluginFixtures');
+const { createPlanFromJSON } = require('../../../appActions');
+
+const testPlan = {
+ "TEST_GROUP": [
+ {
+ "name": "Past event 1",
+ "start": 1660320408000,
+ "end": 1660343797000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 2",
+ "start": 1660406808000,
+ "end": 1660429160000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 3",
+ "start": 1660493208000,
+ "end": 1660503981000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 4",
+ "start": 1660579608000,
+ "end": 1660624108000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 5",
+ "start": 1660666008000,
+ "end": 1660681529000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ }
+ ]
+};
+
+test.describe("Plan", () => {
+ test("Create a Plan and display all plan events @unstable", async ({ page }) => {
+ await page.goto('./', { waitUntil: 'networkidle' });
+
+ const plan = await createPlanFromJSON(page, {
+ name: 'Test Plan',
+ json: testPlan
+ });
+ const startBound = testPlan.TEST_GROUP[0].start;
+ const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
+
+ // Switch to fixed time mode with all plan events within the bounds
+ await page.goto(`${plan.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=plan.view`);
+ const eventCount = await page.locator('.activity-bounds').count();
+ expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
+ });
+});
+
diff --git a/e2e/tests/functional/planning/timestrip.e2e.spec.js b/e2e/tests/functional/planning/timestrip.e2e.spec.js
new file mode 100644
index 000000000..e1f751144
--- /dev/null
+++ b/e2e/tests/functional/planning/timestrip.e2e.spec.js
@@ -0,0 +1,181 @@
+/*****************************************************************************
+ * Open MCT, Copyright (c) 2014-2022, United States Government
+ * as represented by the Administrator of the National Aeronautics and Space
+ * Administration. All rights reserved.
+ *
+ * Open MCT is licensed under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * Open MCT includes source code licensed under additional open source
+ * licenses. See the Open Source Licenses file (LICENSES.md) included with
+ * this source code distribution or the Licensing information page available
+ * at runtime from the About dialog for additional information.
+ *****************************************************************************/
+
+const { test, expect } = require('../../../pluginFixtures');
+const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
+
+const testPlan = {
+ "TEST_GROUP": [
+ {
+ "name": "Past event 1",
+ "start": 1660320408000,
+ "end": 1660343797000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 2",
+ "start": 1660406808000,
+ "end": 1660429160000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 3",
+ "start": 1660493208000,
+ "end": 1660503981000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 4",
+ "start": 1660579608000,
+ "end": 1660624108000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ },
+ {
+ "name": "Past event 5",
+ "start": 1660666008000,
+ "end": 1660681529000,
+ "type": "TEST-GROUP",
+ "color": "orange",
+ "textColor": "white"
+ }
+ ]
+};
+
+test.describe("Time Strip", () => {
+ test("Create two Time Strips, add a single Plan to both, and verify they can have separate Indepdenent Time Contexts @unstable", async ({ page }) => {
+ test.info().annotations.push({
+ type: 'issue',
+ description: 'https://github.com/nasa/openmct/issues/5627'
+ });
+
+ // Constant locators
+ const independentTimeConductorInputs = page.locator('.l-shell__main-independent-time-conductor .c-input--datetime');
+ const activityBounds = page.locator('.activity-bounds');
+
+ // Goto baseURL
+ await page.goto('./', { waitUntil: 'networkidle' });
+
+ const timestrip = await test.step("Create a Time Strip", async () => {
+ const createdTimeStrip = await createDomainObjectWithDefaults(page, { type: 'Time Strip' });
+ const objectName = await page.locator('.l-browse-bar__object-name').innerText();
+ expect(objectName).toBe(createdTimeStrip.name);
+
+ return createdTimeStrip;
+ });
+
+ const plan = await test.step("Create a Plan and add it to the timestrip", async () => {
+ const createdPlan = await createPlanFromJSON(page, {
+ name: 'Test Plan',
+ json: testPlan
+ });
+
+ await page.goto(timestrip.url);
+ // Expand the tree to show the plan
+ await page.click("button[title='Show selected item in tree']");
+ await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view');
+ await page.click("button[title='Save']");
+ await page.click("li[title='Save and Finish Editing']");
+ const startBound = testPlan.TEST_GROUP[0].start;
+ const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
+
+ // Switch to fixed time mode with all plan events within the bounds
+ await page.goto(`${timestrip.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=time-strip.view`);
+
+ // Verify all events are displayed
+ const eventCount = await page.locator('.activity-bounds').count();
+ expect(eventCount).toEqual(testPlan.TEST_GROUP.length);
+
+ return createdPlan;
+ });
+
+ 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);
+
+ // Set the independent time bounds so that only one event is shown
+ const startBound = testPlan.TEST_GROUP[0].start;
+ const endBound = testPlan.TEST_GROUP[0].end;
+ 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');
+ expect(await activityBounds.count()).toEqual(1);
+ });
+
+ await test.step("Can have multiple TimeStrips with the same plan linked and different Independent Time Contexts", async () => {
+ // Create another Time Strip and verify that it has been created
+ const createdTimeStrip = await createDomainObjectWithDefaults(page, {
+ type: 'Time Strip',
+ name: "Another Time Strip"
+ });
+
+ const objectName = await page.locator('.l-browse-bar__object-name').innerText();
+ expect(objectName).toBe(createdTimeStrip.name);
+
+ // Drag the existing Plan onto the newly created Time Strip, and save.
+ await page.dragAndDrop(`role=treeitem[name=/${plan.name}/]`, '.c-object-view');
+ 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);
+
+ // Set the independent time bounds so that two events are shown
+ const startBound = testPlan.TEST_GROUP[0].start;
+ const endBound = testPlan.TEST_GROUP[1].end;
+ 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');
+
+ // Verify that two events are displayed
+ expect(await activityBounds.count()).toEqual(2);
+
+ // Switch to the previous Time Strip and verify that only one event is displayed
+ await page.goto(timestrip.url);
+ expect(await activityBounds.count()).toEqual(1);
+ });
+ });
+});
diff --git a/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js b/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js
index 8bf08e0c9..ff35dc79b 100644
--- a/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js
+++ b/e2e/tests/functional/plugins/faultManagement/faultManagement.e2e.spec.js
@@ -28,14 +28,14 @@ test.describe('The Fault Management Plugin using example faults', () => {
await utils.navigateToFaultManagementWithExample(page);
});
- test('Shows a criticality icon for every fault', async ({ page }) => {
+ test('Shows a criticality icon for every fault @unstable', async ({ page }) => {
const faultCount = await page.locator('c-fault-mgmt__list').count();
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count();
expect.soft(faultCount).toEqual(criticalityIconCount);
});
- test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector', async ({ page }) => {
+ test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({ page }) => {
await utils.selectFaultItem(page, 1);
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent();
@@ -45,7 +45,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(inspectorFaultNameCount).toEqual(1);
});
- test('When selecting multiple faults, no specific fault information is shown in the inspector', async ({ page }) => {
+ test('When selecting multiple faults, no specific fault information is shown in the inspector @unstable', async ({ page }) => {
await utils.selectFaultItem(page, 1);
await utils.selectFaultItem(page, 2);
@@ -61,7 +61,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(secondNameInInspectorCount).toEqual(0);
});
- test('Allows you to shelve a fault', async ({ page }) => {
+ test('Allows you to shelve a fault @unstable', async ({ page }) => {
const shelvedFaultName = await utils.getFaultName(page, 2);
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName);
@@ -80,7 +80,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(await shelvedViewFault.count()).toBe(1);
});
- test('Allows you to acknowledge a fault', async ({ page }) => {
+ test('Allows you to acknowledge a fault @unstable', async ({ page }) => {
const acknowledgedFaultName = await utils.getFaultName(page, 3);
await utils.acknowledgeFault(page, 3);
@@ -94,7 +94,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
});
- test('Allows you to shelve multiple faults', async ({ page }) => {
+ test('Allows you to shelve multiple faults @unstable', async ({ page }) => {
const shelvedFaultNameOne = await utils.getFaultName(page, 1);
const shelvedFaultNameFour = await utils.getFaultName(page, 4);
@@ -121,7 +121,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(await shelvedViewFaultFour.count()).toBe(1);
});
- test('Allows you to acknowledge multiple faults', async ({ page }) => {
+ test('Allows you to acknowledge multiple faults @unstable', async ({ page }) => {
const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2);
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5);
@@ -143,7 +143,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(await acknowledgedViewFaultFive.count()).toBe(1);
});
- test('Allows you to search faults', async ({ page }) => {
+ test('Allows you to search faults @unstable', async ({ page }) => {
const faultThreeNamespace = await utils.getFaultNamespace(page, 3);
const faultTwoName = await utils.getFaultName(page, 2);
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5);
@@ -184,7 +184,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
});
- test('Allows you to sort faults', async ({ page }) => {
+ test('Allows you to sort faults @unstable', async ({ page }) => {
const highestSeverity = await utils.getHighestSeverity(page);
const lowestSeverity = await utils.getLowestSeverity(page);
const faultOneName = 'Example Fault 1';
@@ -213,7 +213,7 @@ test.describe('The Fault Management Plugin without using example faults', () =>
await utils.navigateToFaultManagementWithoutExample(page);
});
- test('Shows no faults when no faults are provided', async ({ page }) => {
+ test('Shows no faults when no faults are provided @unstable', async ({ page }) => {
const faultCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(faultCount).toEqual(0);
@@ -227,7 +227,7 @@ test.describe('The Fault Management Plugin without using example faults', () =>
expect.soft(shelvedCount).toEqual(0);
});
- test('Will return no faults when searching', async ({ page }) => {
+ test('Will return no faults when searching @unstable', async ({ page }) => {
await utils.enterSearchTerm(page, 'fault');
const faultCount = await page.locator('c-fault-mgmt__list').count();
diff --git a/package.json b/package.json
index ac1e3c838..b901f58d2 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"@braintree/sanitize-url": "6.0.0",
"@percy/cli": "1.7.2",
"@percy/playwright": "1.0.4",
- "@playwright/test": "1.23.0",
+ "@playwright/test": "1.25.0",
"@types/eventemitter3": "^1.0.0",
"@types/jasmine": "^4.0.1",
"@types/karma": "^6.3.2",
diff --git a/src/api/time/TimeAPI.js b/src/api/time/TimeAPI.js
index c7e3ddeff..d22361d8d 100644
--- a/src/api/time/TimeAPI.js
+++ b/src/api/time/TimeAPI.js
@@ -171,27 +171,38 @@ class TimeAPI extends GlobalTimeContext {
* @memberof module:openmct.TimeAPI#
* @method getContextForView
*/
- getContextForView(objectPath = []) {
+ getContextForView(objectPath) {
+ if (!objectPath || !Array.isArray(objectPath)) {
+ throw new Error('No objectPath provided');
+ }
+
const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
- if (viewKey) {
- let viewTimeContext = this.getIndependentContext(viewKey);
- if (viewTimeContext) {
- this.independentContexts.delete(viewKey);
- } else {
- viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
- }
+ if (!viewKey) {
+ // Return the global time context
+ return this;
+ }
- // return a new IndependentContext in case the objectPath is different
+ let viewTimeContext = this.getIndependentContext(viewKey);
+ if (!viewTimeContext) {
+ // If the context doesn't exist yet, create it.
+ viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
this.independentContexts.set(viewKey, viewTimeContext);
+ } else {
+ // If it already exists, compare the objectPath to see if it needs to be updated.
+ const currentPath = this.openmct.objects.getRelativePath(viewTimeContext.objectPath);
+ const newPath = this.openmct.objects.getRelativePath(objectPath);
- return viewTimeContext;
+ if (currentPath !== newPath) {
+ // If the path has changed, update the context.
+ this.independentContexts.delete(viewKey);
+ viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
+ this.independentContexts.set(viewKey, viewTimeContext);
+ }
}
- // always follow the global time context
- return this;
+ return viewTimeContext;
}
-
}
export default TimeAPI;
diff --git a/src/plugins/remoteClock/requestInterceptor.js b/src/plugins/remoteClock/requestInterceptor.js
index d6cffe7b3..a04712910 100644
--- a/src/plugins/remoteClock/requestInterceptor.js
+++ b/src/plugins/remoteClock/requestInterceptor.js
@@ -20,17 +20,15 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
-function remoteClockRequestInterceptor(openmct, remoteClockIdentifier, waitForBounds) {
+function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
let remoteClockLoaded = false;
return {
appliesTo: () => {
// Get the activeClock from the Global Time Context
- const { activeClock } = openmct.time.getContextForView();
+ const { activeClock } = openmct.time;
- return activeClock !== undefined
- && activeClock.key === 'remote-clock'
- && !remoteClockLoaded;
+ return activeClock?.key === 'remote-clock' && !remoteClockLoaded;
},
invoke: async (request) => {
const { start, end } = await waitForBounds();
diff --git a/src/plugins/timeConductor/ConductorInputsFixed.vue b/src/plugins/timeConductor/ConductorInputsFixed.vue
index 106e4cee8..c3c14f72e 100644
--- a/src/plugins/timeConductor/ConductorInputsFixed.vue
+++ b/src/plugins/timeConductor/ConductorInputsFixed.vue
@@ -78,6 +78,12 @@ export default {
default() {
return undefined;
}
+ },
+ objectPath: {
+ type: Array,
+ default() {
+ return [];
+ }
}
},
data() {
@@ -127,7 +133,7 @@ export default {
methods: {
setTimeContext() {
this.stopFollowingTimeContext();
- this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
+ this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
this.handleNewBounds(this.timeContext.bounds());
this.timeContext.on('bounds', this.handleNewBounds);
diff --git a/src/plugins/timeConductor/ConductorInputsRealtime.vue b/src/plugins/timeConductor/ConductorInputsRealtime.vue
index c6aee38ee..4054869be 100644
--- a/src/plugins/timeConductor/ConductorInputsRealtime.vue
+++ b/src/plugins/timeConductor/ConductorInputsRealtime.vue
@@ -90,6 +90,12 @@ export default {
return undefined;
}
},
+ objectPath: {
+ type: Array,
+ default() {
+ return [];
+ }
+ },
inputBounds: {
type: Object,
default() {
@@ -162,7 +168,7 @@ export default {
},
setTimeContext() {
this.stopFollowingTime();
- this.timeContext = this.openmct.time.getContextForView(this.keyString ? [{identifier: this.keyString}] : []);
+ this.timeContext = this.openmct.time.getContextForView(this.keyString ? this.objectPath : []);
this.followTime();
},
handleNewBounds(bounds) {
diff --git a/src/plugins/timeConductor/independent/IndependentTimeConductor.vue b/src/plugins/timeConductor/independent/IndependentTimeConductor.vue
index c64b0c38d..8e40e9070 100644
--- a/src/plugins/timeConductor/independent/IndependentTimeConductor.vue
+++ b/src/plugins/timeConductor/independent/IndependentTimeConductor.vue
@@ -52,12 +52,14 @@
<conductor-inputs-fixed
v-if="isFixed"
:key-string="domainObject.identifier.key"
+ :object-path="objectPath"
@updated="saveFixedOffsets"
/>
<conductor-inputs-realtime
v-else
:key-string="domainObject.identifier.key"
+ :object-path="objectPath"
@updated="saveClockOffsets"
/>
</div>
@@ -85,6 +87,10 @@ export default {
domainObject: {
type: Object,
required: true
+ },
+ objectPath: {
+ type: Array,
+ required: true
}
},
data() {
@@ -164,7 +170,7 @@ export default {
},
setTimeContext() {
this.stopFollowingTimeContext();
- this.timeContext = this.openmct.time.getContextForView([this.domainObject]);
+ this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.timeContext.on('clock', this.setTimeOptions);
},
stopFollowingTimeContext() {
diff --git a/src/ui/components/ObjectView.vue b/src/ui/components/ObjectView.vue
index 2976f7dc7..1ab45a947 100644
--- a/src/ui/components/ObjectView.vue
+++ b/src/ui/components/ObjectView.vue
@@ -6,6 +6,7 @@
>
<independent-time-conductor
:domain-object="domainObject"
+ :object-path="path"
@stateChanged="updateIndependentTimeState"
@updated="saveTimeOptions"
/>
@@ -67,6 +68,9 @@ export default {
};
},
computed: {
+ path() {
+ return this.domainObject && (this.currentObjectPath || this.objectPath);
+ },
objectFontStyle() {
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
},