Compare commits
20 Commits
plots-prog
...
test-data
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
044a4c5bae | ||
|
|
a6c841b46c | ||
|
|
9cffe376cd | ||
|
|
ad929ce411 | ||
|
|
98e61ad43a | ||
|
|
f30d56be21 | ||
|
|
2bc327db95 | ||
|
|
ec8117fc8b | ||
|
|
dd94251ca7 | ||
|
|
961aa028bf | ||
|
|
1922269f0e | ||
|
|
3ff1576cbb | ||
|
|
5c67414f4e | ||
|
|
f162573874 | ||
|
|
1084c360a7 | ||
|
|
9f25eb68d9 | ||
|
|
b215f00c03 | ||
|
|
5a1c329c66 | ||
|
|
00a5cbd2fd | ||
|
|
a2d698d5c1 |
@@ -28,9 +28,7 @@ const { test } = require('../../fixtures.js');
|
||||
const { expect } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
|
||||
// https://github.com/nasa/openmct/issues/4323#issuecomment-1067282651
|
||||
|
||||
test.describe('Persistence operations', () => {
|
||||
test.describe('Persistence operations @addInit', () => {
|
||||
// add non persistable root item
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
@@ -38,6 +36,10 @@ test.describe('Persistence operations', () => {
|
||||
});
|
||||
|
||||
test('Persistability should be respected in the create form location field', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/4323'
|
||||
});
|
||||
// Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
|
||||
30
e2e/tests/plugins/gauge/addInitGauge.js
Normal file
30
e2e/tests/plugins/gauge/addInitGauge.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
// this will be called from the test suite with
|
||||
// await page.addInitScript({ path: path.join(__dirname, 'addInitGauge.js') });
|
||||
// it will install the Gauge since it is not installed by default
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const openmct = window.openmct;
|
||||
openmct.install(openmct.plugins.Gauge());
|
||||
});
|
||||
@@ -328,38 +328,12 @@ test('Example Imagery in Display layout', async ({ page }) => {
|
||||
|
||||
return newImageCount;
|
||||
}, {
|
||||
message: "verify that new images still stream in",
|
||||
message: "verify that old images are discarded",
|
||||
timeout: 6 * 1000
|
||||
}).toBeGreaterThan(imageCount);
|
||||
}).toBe(imageCount);
|
||||
|
||||
// Verify selected image is still displayed
|
||||
await expect(selectedImage).toBeVisible();
|
||||
|
||||
// Unpause imagery
|
||||
await page.locator('.pause-play').click();
|
||||
|
||||
//Get background-image url from background-image css prop
|
||||
const backgroundImage = page.locator('.c-imagery__main-image__background-image');
|
||||
let backgroundImageUrl = await backgroundImage.evaluate((el) => {
|
||||
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
||||
});
|
||||
let backgroundImageUrl1 = backgroundImageUrl.slice(1, -1); //forgive me, padre
|
||||
console.log('backgroundImageUrl1 ' + backgroundImageUrl1);
|
||||
|
||||
let backgroundImageUrl2;
|
||||
await expect.poll(async () => {
|
||||
// Verify next image has updated
|
||||
let backgroundImageUrlNext = await backgroundImage.evaluate((el) => {
|
||||
return window.getComputedStyle(el).getPropertyValue('background-image').match(/url\(([^)]+)\)/)[1];
|
||||
});
|
||||
backgroundImageUrl2 = backgroundImageUrlNext.slice(1, -1); //forgive me, padre
|
||||
|
||||
return backgroundImageUrl2;
|
||||
}, {
|
||||
message: "verify next image has updated",
|
||||
timeout: 6 * 1000
|
||||
}).not.toBe(backgroundImageUrl1);
|
||||
console.log('backgroundImageUrl2 ' + backgroundImageUrl2);
|
||||
});
|
||||
|
||||
test.describe('Example imagery thumbnails resize in display layouts', () => {
|
||||
|
||||
@@ -125,17 +125,17 @@ async function openContextMenuRestrictedNotebook(page) {
|
||||
return;
|
||||
}
|
||||
|
||||
test.describe('Restricted Notebook', () => {
|
||||
test.describe('Restricted Notebook @addInit', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await startAndAddNotebookObject(page);
|
||||
});
|
||||
|
||||
test('Can be renamed', async ({ page }) => {
|
||||
test('Can be renamed @addInit', async ({ page }) => {
|
||||
await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText(`Unnamed ${CUSTOM_NAME}`);
|
||||
});
|
||||
|
||||
test('Can be deleted if there are no locked pages', async ({ page }) => {
|
||||
test('Can be deleted if there are no locked pages @addInit', async ({ page }) => {
|
||||
await openContextMenuRestrictedNotebook(page);
|
||||
|
||||
const menuOptions = page.locator('.c-menu ul');
|
||||
@@ -158,7 +158,7 @@ test.describe('Restricted Notebook', () => {
|
||||
expect.soft(await restrictedNotebookTreeObject.count()).toEqual(0);
|
||||
});
|
||||
|
||||
test('Can be locked if at least one page has one entry', async ({ page }) => {
|
||||
test('Can be locked if at least one page has one entry @addInit', async ({ page }) => {
|
||||
|
||||
await enterTextEntry(page);
|
||||
|
||||
@@ -168,7 +168,7 @@ test.describe('Restricted Notebook', () => {
|
||||
|
||||
});
|
||||
|
||||
test.describe('Restricted Notebook with at least one entry and with the page locked', () => {
|
||||
test.describe('Restricted Notebook with at least one entry and with the page locked @addInit', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await startAndAddNotebookObject(page);
|
||||
@@ -179,7 +179,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
await page.locator('button.c-notebook__toggle-nav-button').click();
|
||||
});
|
||||
|
||||
test('Locked page should now be in a locked state', async ({ page }) => {
|
||||
test('Locked page should now be in a locked state @addInit', async ({ page }) => {
|
||||
// main lock message on page
|
||||
const lockMessage = page.locator('text=This page has been committed and cannot be modified or removed');
|
||||
expect.soft(await lockMessage.count()).toEqual(1);
|
||||
@@ -197,7 +197,7 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
|
||||
});
|
||||
|
||||
test('Can still: add page, rename, add entry, delete unlocked pages', async ({ page }) => {
|
||||
test('Can still: add page, rename, add entry, delete unlocked pages @addInit', async ({ page }) => {
|
||||
// Click text=Page Add >> button
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
@@ -237,14 +237,14 @@ test.describe('Restricted Notebook with at least one entry and with the page loc
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Restricted Notebook with a page locked and with an embed', () => {
|
||||
test.describe('Restricted Notebook with a page locked and with an embed @addInit', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await startAndAddNotebookObject(page);
|
||||
await dragAndDropEmbed(page);
|
||||
});
|
||||
|
||||
test('Allows embeds to be deleted if page unlocked', async ({ page }) => {
|
||||
test('Allows embeds to be deleted if page unlocked @addInit', async ({ page }) => {
|
||||
// Click .c-ne__embed__name .c-popup-menu-button
|
||||
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
||||
|
||||
@@ -252,7 +252,7 @@ test.describe('Restricted Notebook with a page locked and with an embed', () =>
|
||||
await expect.soft(embedMenu).toContainText('Remove This Embed');
|
||||
});
|
||||
|
||||
test('Disallows embeds to be deleted if page locked', async ({ page }) => {
|
||||
test('Disallows embeds to be deleted if page locked @addInit', async ({ page }) => {
|
||||
await lockPage(page);
|
||||
// Click .c-ne__embed__name .c-popup-menu-button
|
||||
await page.locator('.c-ne__embed__name .c-popup-menu-button').click(); // embed popup menu
|
||||
|
||||
184
e2e/tests/plugins/timer/timer.e2e.spec.js
Normal file
184
e2e/tests/plugins/timer/timer.e2e.spec.js
Normal file
@@ -0,0 +1,184 @@
|
||||
/*****************************************************************************
|
||||
* 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 } = require('../../../fixtures.js');
|
||||
const { expect } = require('@playwright/test');
|
||||
|
||||
test.describe('Timer', () => {
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Click 'Timer'
|
||||
await page.click('text=Timer');
|
||||
|
||||
// Click text=OK
|
||||
await Promise.all([
|
||||
page.waitForNavigation({waitUntil: 'networkidle'}),
|
||||
page.click('text=OK')
|
||||
]);
|
||||
|
||||
await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Timer');
|
||||
});
|
||||
|
||||
test('Can perform actions on the Timer', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/nasa/openmct/issues/4313'
|
||||
});
|
||||
|
||||
await test.step("From the tree context menu", async () => {
|
||||
await triggerTimerContextMenuAction(page, 'Start');
|
||||
await triggerTimerContextMenuAction(page, 'Pause');
|
||||
await triggerTimerContextMenuAction(page, 'Restart at 0');
|
||||
await triggerTimerContextMenuAction(page, 'Stop');
|
||||
});
|
||||
|
||||
await test.step("From the 3dot menu", async () => {
|
||||
await triggerTimer3dotMenuAction(page, 'Start');
|
||||
await triggerTimer3dotMenuAction(page, 'Pause');
|
||||
await triggerTimer3dotMenuAction(page, 'Restart at 0');
|
||||
await triggerTimer3dotMenuAction(page, 'Stop');
|
||||
});
|
||||
|
||||
await test.step("From the object view", async () => {
|
||||
await triggerTimerViewAction(page, 'Start');
|
||||
await triggerTimerViewAction(page, 'Pause');
|
||||
await triggerTimerViewAction(page, 'Restart at 0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Actions that can be performed on a timer from context menus.
|
||||
* @typedef {'Start' | 'Stop' | 'Pause' | 'Restart at 0'} TimerAction
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actions that can be performed on a timer from the object view.
|
||||
* @typedef {'Start' | 'Pause' | 'Restart at 0'} TimerViewAction
|
||||
*/
|
||||
|
||||
/**
|
||||
* Open the timer context menu from the object tree.
|
||||
* Expands the 'My Items' folder if it is not already expanded.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
async function openTimerContextMenu(page) {
|
||||
const myItemsFolder = page.locator('text=Open MCT My Items >> span').nth(3);
|
||||
const className = await myItemsFolder.getAttribute('class');
|
||||
if (!className.includes('c-disclosure-triangle--expanded')) {
|
||||
await myItemsFolder.click();
|
||||
}
|
||||
|
||||
await page.locator(`a:has-text("Unnamed Timer")`).click({
|
||||
button: 'right'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer action from the tree context menu
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerAction} action
|
||||
*/
|
||||
async function triggerTimerContextMenuAction(page, action) {
|
||||
const menuAction = `.c-menu ul li >> text="${action}"`;
|
||||
await openTimerContextMenu(page);
|
||||
await page.locator(menuAction).click();
|
||||
assertTimerStateAfterAction(page, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer action from the 3dot menu
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerAction} action
|
||||
*/
|
||||
async function triggerTimer3dotMenuAction(page, action) {
|
||||
const menuAction = `.c-menu ul li >> text="${action}"`;
|
||||
const threeDotMenuButton = 'button[title="More options"]';
|
||||
let isActionAvailable = false;
|
||||
let iterations = 0;
|
||||
// Dismiss/open the 3dot menu until the action is available
|
||||
// or a maxiumum number of iterations is reached
|
||||
while (!isActionAvailable && iterations <= 20) {
|
||||
await page.click('.c-object-view');
|
||||
await page.click(threeDotMenuButton);
|
||||
isActionAvailable = await page.locator(menuAction).isVisible();
|
||||
iterations++;
|
||||
}
|
||||
|
||||
await page.locator(menuAction).click();
|
||||
assertTimerStateAfterAction(page, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer action from the object view
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerViewAction} action
|
||||
*/
|
||||
async function triggerTimerViewAction(page, action) {
|
||||
const buttonTitle = buttonTitleFromAction(action);
|
||||
await page.click(`button[title="${buttonTitle}"]`);
|
||||
assertTimerStateAfterAction(page, action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a TimerViewAction and returns the button title
|
||||
* @param {TimerViewAction} action
|
||||
*/
|
||||
function buttonTitleFromAction(action) {
|
||||
switch (action) {
|
||||
case 'Start':
|
||||
return 'Start';
|
||||
case 'Pause':
|
||||
return 'Pause';
|
||||
case 'Restart at 0':
|
||||
return 'Reset';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the timer state after a timer action has been performed.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {TimerAction} action
|
||||
*/
|
||||
async function assertTimerStateAfterAction(page, action) {
|
||||
let timerStateClass;
|
||||
switch (action) {
|
||||
case 'Start':
|
||||
case 'Restart at 0':
|
||||
timerStateClass = "is-started";
|
||||
break;
|
||||
case 'Stop':
|
||||
timerStateClass = 'is-stopped';
|
||||
break;
|
||||
case 'Pause':
|
||||
timerStateClass = 'is-paused';
|
||||
break;
|
||||
}
|
||||
|
||||
await expect.soft(page.locator('.c-timer')).toHaveClass(new RegExp(timerStateClass));
|
||||
}
|
||||
72
e2e/tests/visual/addInit.visual.spec.js
Normal file
72
e2e/tests/visual/addInit.visual.spec.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/* eslint-disable no-undef */
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
/*
|
||||
Collection of Visual Tests set to run with modified init scripts to inject plugins not otherwise available in the default contexts.
|
||||
|
||||
These should only use functional expect statements to verify assumptions about the state
|
||||
in a test and not for functional verification of correctness. Visual tests are not supposed
|
||||
to "fail" on assertions. Instead, they should be used to detect changes between builds or branches.
|
||||
|
||||
Note: Larger testsuite sizes are OK due to the setup time associated with these tests.
|
||||
*/
|
||||
|
||||
const { test } = require('@playwright/test');
|
||||
const percySnapshot = require('@percy/playwright');
|
||||
const path = require('path');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const VISUAL_GRACE_PERIOD = 5 * 1000; //Lets the application "simmer" before the snapshot is taken
|
||||
|
||||
// Snippet from https://github.com/microsoft/playwright/issues/6347#issuecomment-965887758
|
||||
// Will replace with cy.clock() equivalent
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await context.addInitScript({
|
||||
path: path.join(__dirname, '../../..', './node_modules/sinon/pkg/sinon.js')
|
||||
});
|
||||
await context.addInitScript(() => {
|
||||
window.__clock = sinon.useFakeTimers({
|
||||
now: 0,
|
||||
shouldAdvanceTime: true
|
||||
}); //Set browser clock to UNIX Epoch
|
||||
});
|
||||
});
|
||||
|
||||
test('Visual - Default Gauge is correct @addInit', async ({ page }) => {
|
||||
|
||||
await page.addInitScript({ path: path.join(__dirname, '../plugins/gauge', './addInitGauge.js') });
|
||||
//Go to baseURL
|
||||
await page.goto('/', { waitUntil: 'networkidle' });
|
||||
|
||||
//Click the Create button
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
await page.click('text=Gauge');
|
||||
|
||||
await page.click('text=OK');
|
||||
|
||||
// Take a snapshot of the newly created Gauge object
|
||||
await page.waitForTimeout(VISUAL_GRACE_PERIOD);
|
||||
await percySnapshot(page, 'Default Gauge');
|
||||
|
||||
});
|
||||
@@ -168,7 +168,7 @@ function getImageUrlListFromConfig(configuration) {
|
||||
}
|
||||
|
||||
function getImageLoadDelay(domainObject) {
|
||||
const imageLoadDelay = domainObject.configuration.imageLoadDelayInMilliSeconds;
|
||||
const imageLoadDelay = Math.trunc(Number(domainObject.configuration.imageLoadDelayInMilliSeconds));
|
||||
if (!imageLoadDelay) {
|
||||
openmctInstance.objects.mutate(domainObject, 'configuration.imageLoadDelayInMilliSeconds', DEFAULT_IMAGE_LOAD_DELAY_IN_MILISECONDS);
|
||||
|
||||
@@ -190,7 +190,9 @@ function getRealtimeProvider() {
|
||||
subscribe: (domainObject, callback) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
const interval = setInterval(() => {
|
||||
callback(pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay));
|
||||
const imageSamples = getImageSamples(domainObject.configuration);
|
||||
const datum = pointForTimestamp(Date.now(), domainObject.name, imageSamples, delay);
|
||||
callback(datum);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
@@ -229,8 +231,9 @@ function getLadProvider() {
|
||||
},
|
||||
request: (domainObject, options) => {
|
||||
const delay = getImageLoadDelay(domainObject);
|
||||
const datum = pointForTimestamp(Date.now(), domainObject.name, getImageSamples(domainObject.configuration), delay);
|
||||
|
||||
return Promise.resolve([pointForTimestamp(Date.now(), domainObject.name, delay)]);
|
||||
return Promise.resolve([datum]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,8 +85,6 @@ class ActionCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.removeAllListeners();
|
||||
|
||||
if (!this.skipEnvironmentObservers) {
|
||||
this.objectUnsubscribes.forEach(unsubscribe => {
|
||||
unsubscribe();
|
||||
@@ -96,6 +94,7 @@ class ActionCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
this.emit('destroy', this.view);
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
getVisibleActions() {
|
||||
|
||||
@@ -339,6 +339,8 @@ define([
|
||||
|
||||
return provider.request.apply(provider, arguments)
|
||||
.catch((rejected) => {
|
||||
console.log('telemetryAPI rejected!!!!!');
|
||||
console.log(rejected);
|
||||
if (rejected.name !== 'AbortError') {
|
||||
this.openmct.notifications.error('Error requesting telemetry data, see console for details');
|
||||
console.error(rejected);
|
||||
|
||||
@@ -30,7 +30,7 @@ export default {
|
||||
this.timeSystemChange = this.timeSystemChange.bind(this);
|
||||
this.setDataTimeContext = this.setDataTimeContext.bind(this);
|
||||
this.setDataTimeContext();
|
||||
this.openmct.objectViews.on('clearData', this.clearData);
|
||||
this.openmct.objectViews.on('clearData', this.dataCleared);
|
||||
|
||||
// set
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
@@ -44,8 +44,11 @@ export default {
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
|
||||
// kickoff
|
||||
this.subscribe();
|
||||
this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, {});
|
||||
this.telemetryCollection.on('add', this.dataAdded);
|
||||
this.telemetryCollection.on('remove', this.dataRemoved);
|
||||
this.telemetryCollection.on('clear', this.dataCleared);
|
||||
this.telemetryCollection.load();
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unsubscribe) {
|
||||
@@ -54,9 +57,31 @@ export default {
|
||||
}
|
||||
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.openmct.objectViews.off('clearData', this.clearData);
|
||||
this.openmct.objectViews.off('clearData', this.dataCleared);
|
||||
|
||||
this.telemetryCollection.off('add', this.dataAdded);
|
||||
this.telemetryCollection.off('remove', this.dataRemoved);
|
||||
this.telemetryCollection.off('clear', this.dataCleared);
|
||||
|
||||
this.telemetryCollection.destroy();
|
||||
},
|
||||
methods: {
|
||||
dataAdded(dataToAdd) {
|
||||
const normalizedDataToAdd = dataToAdd.map(datum => this.normalizeDatum(datum));
|
||||
this.imageHistory = this.imageHistory.concat(normalizedDataToAdd);
|
||||
},
|
||||
dataCleared() {
|
||||
this.imageHistory = [];
|
||||
},
|
||||
dataRemoved(dataToRemove) {
|
||||
this.imageHistory = this.imageHistory.filter(existingDatum => {
|
||||
const shouldKeep = dataToRemove.some(datumToRemove => {
|
||||
return (existingDatum.utc !== datumToRemove.utc);
|
||||
});
|
||||
|
||||
return shouldKeep;
|
||||
});
|
||||
},
|
||||
setDataTimeContext() {
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
@@ -70,19 +95,6 @@ export default {
|
||||
this.timeContext.off('timeSystem', this.timeSystemChange);
|
||||
}
|
||||
},
|
||||
isDatumValid(datum) {
|
||||
//TODO: Add a check to see if there are duplicate images (identical image timestamp and url subsequently)
|
||||
if (!datum) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const datumTimeCheck = this.parseTime(datum);
|
||||
const bounds = this.timeContext.bounds();
|
||||
|
||||
const isOutOfBounds = datumTimeCheck < bounds.start || datumTimeCheck > bounds.end;
|
||||
|
||||
return !isOutOfBounds;
|
||||
},
|
||||
formatImageUrl(datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
@@ -124,40 +136,6 @@ export default {
|
||||
// forcibly reset the imageContainer size to prevent an aspect ratio distortion
|
||||
delete this.imageContainerWidth;
|
||||
delete this.imageContainerHeight;
|
||||
|
||||
return this.requestHistory();
|
||||
},
|
||||
async requestHistory() {
|
||||
this.requestCount++;
|
||||
const requestId = this.requestCount;
|
||||
const bounds = this.timeContext.bounds();
|
||||
|
||||
const data = await this.openmct.telemetry
|
||||
.request(this.domainObject, bounds) || [];
|
||||
// wait until new request resolves to do comparison
|
||||
if (this.requestCount !== requestId) {
|
||||
return this.imageHistory = [];
|
||||
}
|
||||
|
||||
const imagery = data.filter(this.isDatumValid).map(this.normalizeDatum);
|
||||
this.imageHistory = imagery;
|
||||
},
|
||||
clearData(domainObjectToClear) {
|
||||
// global clearData button is accepted therefore no truthy check on inputted param
|
||||
const clearDataForObjectSelected = Boolean(domainObjectToClear);
|
||||
if (clearDataForObjectSelected) {
|
||||
const idsEqual = this.openmct.objects.areIdsEqual(
|
||||
domainObjectToClear.identifier,
|
||||
this.domainObject.identifier
|
||||
);
|
||||
if (!idsEqual) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// splice array to encourage garbage collection
|
||||
this.imageHistory.splice(0, this.imageHistory.length);
|
||||
|
||||
},
|
||||
timeSystemChange() {
|
||||
this.timeSystem = this.timeContext.timeSystem();
|
||||
@@ -165,22 +143,7 @@ export default {
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
},
|
||||
subscribe() {
|
||||
this.unsubscribe = this.openmct.telemetry
|
||||
.subscribe(this.domainObject, (datum) => {
|
||||
let parsedTimestamp = this.parseTime(datum);
|
||||
let bounds = this.timeContext.bounds();
|
||||
if (!(parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isDatumValid(datum)) {
|
||||
this.imageHistory.push(this.normalizeDatum(datum));
|
||||
}
|
||||
});
|
||||
},
|
||||
normalizeDatum(datum) {
|
||||
|
||||
const formattedTime = this.formatTime(datum);
|
||||
const url = this.formatImageUrl(datum);
|
||||
const time = this.parseTime(formattedTime);
|
||||
|
||||
@@ -88,6 +88,7 @@ describe("The Imagery View Layouts", () => {
|
||||
let openmct;
|
||||
let parent;
|
||||
let child;
|
||||
let historicalProvider;
|
||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||
let imageryObject = {
|
||||
identifier: {
|
||||
@@ -122,50 +123,6 @@ describe("The Imagery View Layouts", () => {
|
||||
"priority": 3
|
||||
},
|
||||
"source": "url"
|
||||
// "relatedTelemetry": {
|
||||
// "heading": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "heading",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "roll": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "roll",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "pitch": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "pitch",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "cameraPan": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "cameraPan",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "cameraTilt": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "cameraTilt",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// },
|
||||
// "sunOrientation": {
|
||||
// "comparisonFunction": comparisonFunction,
|
||||
// "historical": {
|
||||
// "telemetryObjectId": "sunOrientation",
|
||||
// "valueKey": "value"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
},
|
||||
{
|
||||
"name": "Name",
|
||||
@@ -209,6 +166,13 @@ describe("The Imagery View Layouts", () => {
|
||||
telemetryPromiseResolve = resolve;
|
||||
});
|
||||
|
||||
historicalProvider = {
|
||||
request: () => {
|
||||
return Promise.resolve(imageTelemetry);
|
||||
}
|
||||
};
|
||||
spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider);
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||
telemetryPromiseResolve(imageTelemetry);
|
||||
|
||||
@@ -626,6 +590,20 @@ describe("The Imagery View Layouts", () => {
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
|
||||
const mockClock = jasmine.createSpyObj("clock", [
|
||||
"on",
|
||||
"off",
|
||||
"currentValue"
|
||||
]);
|
||||
mockClock.key = 'mockClock';
|
||||
mockClock.currentValue.and.returnValue(1);
|
||||
|
||||
openmct.time.addClock(mockClock);
|
||||
openmct.time.clock('mockClock', {
|
||||
start: START - (5 * ONE_MINUTE),
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
|
||||
openmct.router.path = [{
|
||||
identifier: {
|
||||
key: 'test-timestrip',
|
||||
@@ -660,7 +638,7 @@ describe("The Imagery View Layouts", () => {
|
||||
it("on mount should show imagery within the given bounds", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(6);
|
||||
expect(imageElements.length).toEqual(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -680,5 +658,46 @@ describe("The Imagery View Layouts", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove images when clock advances", async () => {
|
||||
openmct.time.tick(ONE_MINUTE * 2);
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(4);
|
||||
});
|
||||
|
||||
it("should remove images when start bounds shorten", async () => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START,
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("should remove images when end bounds shorten", async () => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START - (5 * ONE_MINUTE),
|
||||
end: START - (2 * ONE_MINUTE)
|
||||
});
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(4);
|
||||
});
|
||||
|
||||
it("should remove images when both bounds shorten", async () => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START - (2 * ONE_MINUTE),
|
||||
end: START + (2 * ONE_MINUTE)
|
||||
});
|
||||
await Vue.nextTick();
|
||||
await Vue.nextTick();
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
:highlights="highlights"
|
||||
:show-limit-line-labels="showLimitLineLabels"
|
||||
@plotReinitializeCanvas="initCanvas"
|
||||
@chartLoaded="initialize"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -359,11 +360,6 @@ export default {
|
||||
this.setTimeContext();
|
||||
|
||||
this.loaded = true;
|
||||
|
||||
//We're referencing the canvas elements from the mct-chart in the initialize method.
|
||||
// So we need $nextTick to ensure the component is fully mounted before we can initialize stuff.
|
||||
this.$nextTick(this.initialize);
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
|
||||
@@ -105,9 +105,8 @@ export default {
|
||||
let canvasEls = this.$parent.$refs.chartContainer.querySelectorAll("canvas");
|
||||
const mainCanvas = canvasEls[1];
|
||||
const overlayCanvas = canvasEls[0];
|
||||
if (this.initializeCanvas(mainCanvas, overlayCanvas)) {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
const initialized = this.initializeCanvas(mainCanvas, overlayCanvas);
|
||||
|
||||
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
|
||||
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
|
||||
@@ -115,6 +114,12 @@ export default {
|
||||
this.listenTo(this.config.yAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.listenTo(this.config.xAxis, 'change', this.updateLimitsAndDraw);
|
||||
this.config.series.forEach(this.onSeriesAdd, this);
|
||||
|
||||
if (initialized) {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
this.$emit('chartLoaded');
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy();
|
||||
|
||||
@@ -26,13 +26,13 @@
|
||||
class="c-plot c-plot--stacked holder holder-plot has-control-bar"
|
||||
:class="[plotLegendExpandedStateClass, plotLegendPositionClass]"
|
||||
>
|
||||
<plot-legend
|
||||
:cursor-locked="!!lockHighlightPoint"
|
||||
:series="seriesModels"
|
||||
:highlights="highlights"
|
||||
:legend="legend"
|
||||
@legendHoverChanged="legendHoverChanged"
|
||||
/>
|
||||
<!-- <plot-legend-->
|
||||
<!-- :cursor-locked="!!lockHighlightPoint"-->
|
||||
<!-- :series="seriesModels"-->
|
||||
<!-- :highlights="highlights"-->
|
||||
<!-- :legend="legend"-->
|
||||
<!-- @legendHoverChanged="legendHoverChanged"-->
|
||||
<!-- />-->
|
||||
<div class="l-view-section">
|
||||
<stacked-plot-item
|
||||
v-for="object in compositionObjects"
|
||||
@@ -244,11 +244,13 @@ export default {
|
||||
this.highlights = data;
|
||||
},
|
||||
registerSeriesListeners(configId) {
|
||||
this.seriesConfig[configId] = this.getConfig(configId);
|
||||
this.listenTo(this.seriesConfig[configId].series, 'add', this.addSeries, this);
|
||||
this.listenTo(this.seriesConfig[configId].series, 'remove', this.removeSeries, this);
|
||||
// const config = this.getConfig(configId);
|
||||
// config.series.models.forEach(this.addSeries, this);
|
||||
|
||||
this.seriesConfig[configId].series.models.forEach(this.addSeries, this);
|
||||
// if (config.series.models.length === 0 || config.series.models[0].keyString !== configId) {
|
||||
// this.listenTo(config.series, 'add', this.addSeries, this);
|
||||
// this.listenTo(config.series, 'remove', this.removeSeries, this);
|
||||
// }
|
||||
},
|
||||
addSeries(series) {
|
||||
const index = this.seriesModels.length;
|
||||
|
||||
@@ -227,28 +227,25 @@ export default {
|
||||
return this.openmct.objects.areIdsEqual(seriesConfig.identifier, this.childObject.identifier);
|
||||
});
|
||||
|
||||
if (!persistedSeriesConfig) {
|
||||
persistedSeriesConfig = {
|
||||
series: {},
|
||||
yAxis: {}
|
||||
let domainObject = {
|
||||
...this.childObject
|
||||
};
|
||||
if (persistedSeriesConfig) {
|
||||
domainObject.configuration = {
|
||||
series: [
|
||||
{
|
||||
identifier: this.childObject.identifier,
|
||||
...persistedSeriesConfig.series
|
||||
}
|
||||
],
|
||||
yAxis: persistedSeriesConfig.yAxis
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
config = new PlotConfigurationModel({
|
||||
id: configId,
|
||||
domainObject: {
|
||||
...this.childObject,
|
||||
configuration: {
|
||||
series: [
|
||||
{
|
||||
identifier: this.childObject.identifier,
|
||||
...persistedSeriesConfig.series
|
||||
}
|
||||
],
|
||||
yAxis: persistedSeriesConfig.yAxis
|
||||
|
||||
}
|
||||
},
|
||||
domainObject,
|
||||
openmct: this.openmct,
|
||||
palette: this.colorPalette,
|
||||
callback: (data) => {
|
||||
|
||||
@@ -43,14 +43,19 @@ export default class PauseTimerAction {
|
||||
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState === 'started';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState === 'started';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,19 @@ export default class RestartTimerAction {
|
||||
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +63,19 @@ export default class StartTimerAction {
|
||||
newConfiguration.pausedTime = undefined;
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState !== 'started';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState !== 'started';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,14 +44,19 @@ export default class StopTimerAction {
|
||||
|
||||
this.openmct.objects.mutate(domainObject, 'configuration', newConfiguration);
|
||||
}
|
||||
appliesTo(objectPath) {
|
||||
appliesTo(objectPath, view = {}) {
|
||||
const domainObject = objectPath[0];
|
||||
if (!domainObject || !domainObject.configuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use object configuration timerState for viewless context menus,
|
||||
// otherwise manually show/hide based on the view's timerState
|
||||
const viewKey = view.key;
|
||||
const { timerState } = domainObject.configuration;
|
||||
|
||||
return domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
return viewKey
|
||||
? domainObject.type === 'timer'
|
||||
: domainObject.type === 'timer' && timerState !== 'stopped';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,15 @@ export default {
|
||||
return timerSign;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
timerState() {
|
||||
if (!this.viewActionsCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showOrHideAvailableActions();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.configuration && this.configuration.timerState === undefined) {
|
||||
@@ -190,6 +199,9 @@ export default {
|
||||
this.unlisten = ticker.listen(() => {
|
||||
this.openmct.objects.refresh(this.domainObject);
|
||||
});
|
||||
|
||||
this.viewActionsCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
|
||||
this.showOrHideAvailableActions();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
@@ -228,6 +240,22 @@ export default {
|
||||
if (action) {
|
||||
action.invoke(this.objectPath, this.currentView);
|
||||
}
|
||||
},
|
||||
showOrHideAvailableActions() {
|
||||
switch (this.timerState) {
|
||||
case 'started':
|
||||
this.viewActionsCollection.hide(['timer.start']);
|
||||
this.viewActionsCollection.show(['timer.stop', 'timer.pause', 'timer.restart']);
|
||||
break;
|
||||
case 'paused':
|
||||
this.viewActionsCollection.hide(['timer.pause']);
|
||||
this.viewActionsCollection.show(['timer.stop', 'timer.start', 'timer.restart']);
|
||||
break;
|
||||
case 'stopped':
|
||||
this.viewActionsCollection.hide(['timer.stop', 'timer.pause', 'timer.restart']);
|
||||
this.viewActionsCollection.show(['timer.start']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,6 +54,9 @@ const config = {
|
||||
"utils": path.join(__dirname, "src/utils")
|
||||
}
|
||||
},
|
||||
optimization: {
|
||||
minimize: false
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,
|
||||
|
||||
Reference in New Issue
Block a user