Compare commits

...

20 Commits

Author SHA1 Message Date
Joshi
044a4c5bae Don't listen 2022-07-01 16:22:03 -07:00
Joshi
a6c841b46c Hide legend for stacked plot 2022-07-01 14:02:33 -07:00
Joshi
9cffe376cd Remove line to add seriesModels 2022-07-01 00:26:30 -07:00
Joshi
ad929ce411 Remove console logs. Don't add configuration if there is no saved config. Only register listeners for legend if series can be persisted 2022-06-30 23:58:58 -07:00
Joshi
98e61ad43a Revert change to not register series listener 2022-06-30 23:08:45 -07:00
Joshi
f30d56be21 Remove listeners for stacked plot 2022-06-30 22:01:05 -07:00
Joshi
2bc327db95 Revert changes to listeners 2022-06-30 21:54:00 -07:00
Joshi
ec8117fc8b Remove usage of listenTo helper 2022-06-30 21:29:55 -07:00
Joshi
dd94251ca7 Comment out listeners for displayRange 2022-06-30 17:03:33 -07:00
Joshi
961aa028bf Undo object change 2022-06-30 15:36:17 -07:00
Joshi
1922269f0e Add console logs 2022-06-30 11:30:39 -07:00
Joshi
3ff1576cbb Draw only after offsets have been set 2022-06-30 10:59:07 -07:00
Joshi
5c67414f4e Return only the child 2022-06-30 10:18:41 -07:00
Joshi
f162573874 Remove selection for inspector 2022-06-30 09:38:57 -07:00
Joshi
1084c360a7 Initialize the plot only after the chart is ready 2022-06-29 21:44:02 -05:00
Khalid Adil
9f25eb68d9 Add logging to rejected requests 2022-06-29 16:29:40 -05:00
Khalid Adil
b215f00c03 Stop minimization of sources 2022-06-29 04:26:51 -05:00
Jesse Mazzella
5a1c329c66 [Timer] Update 3dot menu actions appropriately (#5387)
* Call `removeAllListeners()` after emit

* Manually show/hide actions if within a view

* remove sneaky `console.log()`

* Add Timer e2e test

* Add to comments

* Avoid hard waits in Timer e2e test

- Assert against timer view state instead of menu options

* Let's also test actions from the Timer view
2022-06-28 19:39:46 +02:00
John Hill
00a5cbd2fd Cherrypicked commits (#5390)
Co-authored-by: unlikelyzero <jchill2@gmail.com>
2022-06-26 06:40:50 -07:00
Scott Bell
a2d698d5c1 Imagery View does not discard old images when they fall out of bounds (#5351)
* change to using telemetry collection

* fix tests

* added more unit tests
2022-06-23 16:04:40 -07:00
21 changed files with 500 additions and 201 deletions

View File

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

View 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());
});

View File

@@ -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', () => {

View File

@@ -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

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

View 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');
});

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,6 +54,9 @@ const config = {
"utils": path.join(__dirname, "src/utils")
}
},
optimization: {
minimize: false
},
plugins: [
new webpack.DefinePlugin({
__OPENMCT_VERSION__: `'${packageDefinition.version}'`,