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>
This commit is contained in:
Jesse Mazzella
2022-09-09 17:31:03 -07:00
committed by GitHub
parent c231c2d7cb
commit a7ea5afa59
15 changed files with 397 additions and 42 deletions

View File

@ -2,7 +2,7 @@ version: 2.1
executors: executors:
pw-focal-development: pw-focal-development:
docker: docker:
- image: mcr.microsoft.com/playwright:v1.23.0-focal - image: mcr.microsoft.com/playwright:v1.25.0-focal
environment: environment:
NODE_ENV: development # Needed to ensure 'dist' folder created and devDependencies installed 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 PERCY_POSTINSTALL_BROWSER: 'true' # Needed to store the percy browser in cache deps

View File

@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16' node-version: '16'
- run: npx playwright@1.23.0 install - run: npx playwright@1.25.0 install
- run: npm install - run: npm install
- run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh - run: sh src/plugins/persistence/couch/replace-localstorage-with-couchdb-indexhtml.sh
- run: npm run test:e2e:couchdb - run: npm run test:e2e:couchdb

View File

@ -30,7 +30,7 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16' node-version: '16'
- run: npx playwright@1.23.0 install - run: npx playwright@1.25.0 install
- run: npx playwright install chrome-beta - run: npx playwright install chrome-beta
- run: npm install - run: npm install
- run: npm run test:e2e:full - run: npm run test:e2e:full

View File

@ -45,6 +45,8 @@
* @property {string} url the relative url to the object (for use with `page.goto()`) * @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 * 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. * in the e2e suite when uninterested in properties of the objects themselves.
@ -100,6 +102,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. * Open the given `domainObject`'s context menu from the object tree.
* Expands the path to the object and scrolls to it if necessary. * 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 // eslint-disable-next-line no-undef
module.exports = { module.exports = {
createDomainObjectWithDefaults, createDomainObjectWithDefaults,
createPlanFromJSON,
openObjectTreeContextMenu, openObjectTreeContextMenu,
getHashUrlToDomainObject, getHashUrlToDomainObject,
getFocusedObjectUuid, getFocusedObjectUuid,

View File

@ -42,7 +42,7 @@ test.describe('Persistence operations @addInit', () => {
button: 'right' 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.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']); await expect(menuOptions).not.toContainText(['Move', 'Duplicate', 'Remove', 'Add New Folder', 'Edit Properties...', 'Export as JSON', 'Import from JSON']);

View File

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

View File

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

View File

@ -28,14 +28,14 @@ test.describe('The Fault Management Plugin using example faults', () => {
await utils.navigateToFaultManagementWithExample(page); 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 faultCount = await page.locator('c-fault-mgmt__list').count();
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count(); const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count();
expect.soft(faultCount).toEqual(criticalityIconCount); 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); await utils.selectFaultItem(page, 1);
const selectedFaultName = await page.locator('.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname').textContent(); 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); 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, 1);
await utils.selectFaultItem(page, 2); await utils.selectFaultItem(page, 2);
@ -61,7 +61,7 @@ test.describe('The Fault Management Plugin using example faults', () => {
expect.soft(secondNameInInspectorCount).toEqual(0); 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 shelvedFaultName = await utils.getFaultName(page, 2);
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName); 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); 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); const acknowledgedFaultName = await utils.getFaultName(page, 3);
await utils.acknowledgeFault(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); 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 shelvedFaultNameOne = await utils.getFaultName(page, 1);
const shelvedFaultNameFour = await utils.getFaultName(page, 4); 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); 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 acknowledgedFaultNameTwo = await utils.getFaultName(page, 2);
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5); 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); 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 faultThreeNamespace = await utils.getFaultNamespace(page, 3);
const faultTwoName = await utils.getFaultName(page, 2); const faultTwoName = await utils.getFaultName(page, 2);
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5); 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); 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 highestSeverity = await utils.getHighestSeverity(page);
const lowestSeverity = await utils.getLowestSeverity(page); const lowestSeverity = await utils.getLowestSeverity(page);
const faultOneName = 'Example Fault 1'; const faultOneName = 'Example Fault 1';
@ -213,7 +213,7 @@ test.describe('The Fault Management Plugin without using example faults', () =>
await utils.navigateToFaultManagementWithoutExample(page); 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(); const faultCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(faultCount).toEqual(0); expect.soft(faultCount).toEqual(0);
@ -227,7 +227,7 @@ test.describe('The Fault Management Plugin without using example faults', () =>
expect.soft(shelvedCount).toEqual(0); 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'); await utils.enterSearchTerm(page, 'fault');
const faultCount = await page.locator('c-fault-mgmt__list').count(); const faultCount = await page.locator('c-fault-mgmt__list').count();

View File

@ -7,7 +7,7 @@
"@braintree/sanitize-url": "6.0.0", "@braintree/sanitize-url": "6.0.0",
"@percy/cli": "1.7.2", "@percy/cli": "1.7.2",
"@percy/playwright": "1.0.4", "@percy/playwright": "1.0.4",
"@playwright/test": "1.23.0", "@playwright/test": "1.25.0",
"@types/eventemitter3": "^1.0.0", "@types/eventemitter3": "^1.0.0",
"@types/jasmine": "^4.0.1", "@types/jasmine": "^4.0.1",
"@types/karma": "^6.3.2", "@types/karma": "^6.3.2",

View File

@ -171,27 +171,38 @@ class TimeAPI extends GlobalTimeContext {
* @memberof module:openmct.TimeAPI# * @memberof module:openmct.TimeAPI#
* @method getContextForView * @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); const viewKey = objectPath.length && this.openmct.objects.makeKeyString(objectPath[0].identifier);
if (viewKey) { if (!viewKey) {
let viewTimeContext = this.getIndependentContext(viewKey); // Return the global time context
if (viewTimeContext) {
this.independentContexts.delete(viewKey);
} else {
viewTimeContext = new IndependentTimeContext(this.openmct, this, objectPath);
}
// return a new IndependentContext in case the objectPath is different
this.independentContexts.set(viewKey, viewTimeContext);
return viewTimeContext;
}
// always follow the global time context
return this; return this;
} }
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);
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);
}
}
return viewTimeContext;
}
} }
export default TimeAPI; export default TimeAPI;

View File

@ -20,17 +20,15 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
function remoteClockRequestInterceptor(openmct, remoteClockIdentifier, waitForBounds) { function remoteClockRequestInterceptor(openmct, _remoteClockIdentifier, waitForBounds) {
let remoteClockLoaded = false; let remoteClockLoaded = false;
return { return {
appliesTo: () => { appliesTo: () => {
// Get the activeClock from the Global Time Context // Get the activeClock from the Global Time Context
const { activeClock } = openmct.time.getContextForView(); const { activeClock } = openmct.time;
return activeClock !== undefined return activeClock?.key === 'remote-clock' && !remoteClockLoaded;
&& activeClock.key === 'remote-clock'
&& !remoteClockLoaded;
}, },
invoke: async (request) => { invoke: async (request) => {
const { start, end } = await waitForBounds(); const { start, end } = await waitForBounds();

View File

@ -78,6 +78,12 @@ export default {
default() { default() {
return undefined; return undefined;
} }
},
objectPath: {
type: Array,
default() {
return [];
}
} }
}, },
data() { data() {
@ -127,7 +133,7 @@ export default {
methods: { methods: {
setTimeContext() { setTimeContext() {
this.stopFollowingTimeContext(); 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.handleNewBounds(this.timeContext.bounds());
this.timeContext.on('bounds', this.handleNewBounds); this.timeContext.on('bounds', this.handleNewBounds);

View File

@ -90,6 +90,12 @@ export default {
return undefined; return undefined;
} }
}, },
objectPath: {
type: Array,
default() {
return [];
}
},
inputBounds: { inputBounds: {
type: Object, type: Object,
default() { default() {
@ -162,7 +168,7 @@ export default {
}, },
setTimeContext() { setTimeContext() {
this.stopFollowingTime(); 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(); this.followTime();
}, },
handleNewBounds(bounds) { handleNewBounds(bounds) {

View File

@ -52,12 +52,14 @@
<conductor-inputs-fixed <conductor-inputs-fixed
v-if="isFixed" v-if="isFixed"
:key-string="domainObject.identifier.key" :key-string="domainObject.identifier.key"
:object-path="objectPath"
@updated="saveFixedOffsets" @updated="saveFixedOffsets"
/> />
<conductor-inputs-realtime <conductor-inputs-realtime
v-else v-else
:key-string="domainObject.identifier.key" :key-string="domainObject.identifier.key"
:object-path="objectPath"
@updated="saveClockOffsets" @updated="saveClockOffsets"
/> />
</div> </div>
@ -85,6 +87,10 @@ export default {
domainObject: { domainObject: {
type: Object, type: Object,
required: true required: true
},
objectPath: {
type: Array,
required: true
} }
}, },
data() { data() {
@ -164,7 +170,7 @@ export default {
}, },
setTimeContext() { setTimeContext() {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView([this.domainObject]); this.timeContext = this.openmct.time.getContextForView(this.objectPath);
this.timeContext.on('clock', this.setTimeOptions); this.timeContext.on('clock', this.setTimeOptions);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {

View File

@ -6,6 +6,7 @@
> >
<independent-time-conductor <independent-time-conductor
:domain-object="domainObject" :domain-object="domainObject"
:object-path="path"
@stateChanged="updateIndependentTimeState" @stateChanged="updateIndependentTimeState"
@updated="saveTimeOptions" @updated="saveTimeOptions"
/> />
@ -67,6 +68,9 @@ export default {
}; };
}, },
computed: { computed: {
path() {
return this.domainObject && (this.currentObjectPath || this.objectPath);
},
objectFontStyle() { objectFontStyle() {
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle; return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
}, },