Decouple removal of independent time context for a view from refreshing context for other views (#6334)

* Decouple removing the context for a view from refreshing the context of views to get their upstream contexts

* Add test for clicking on an item in the elements pool for a preview

* Add test to ensure independent time context for items with no parents works as expected
This commit is contained in:
Shefali Joshi 2023-02-13 13:19:26 -08:00 committed by GitHub
parent 5da1c9c0d7
commit 6d62e0e73c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 145 additions and 5 deletions

View File

@ -140,4 +140,61 @@ test.describe('Overlay Plot', () => {
expect(yAxis3Group.getByRole('listitem', { name: swgB.name })).toBeTruthy();
expect(yAxis3Group.getByRole('listitem').nth(0).getByText(swgB.name)).toBeTruthy();
});
test('Clicking on an item in the elements pool brings up the plot preview with data points', async ({ page }) => {
const overlayPlot = await createDomainObjectWithDefaults(page, {
type: "Overlay Plot"
});
const swgA = await createDomainObjectWithDefaults(page, {
type: "Sine Wave Generator",
parent: overlayPlot.uuid
});
await page.goto(overlayPlot.url);
await page.click('button[title="Edit"]');
await page.locator(`#inspector-elements-tree >> text=${swgA.name}`).click();
await page.locator('.js-overlay canvas').nth(1);
const plotPixelSize = await getCanvasPixelsWithData(page);
expect(plotPixelSize).toBeGreaterThan(0);
});
});
/**
* @param {import('@playwright/test').Page} page
*/
async function getCanvasPixelsWithData(page) {
const getTelemValuePromise = new Promise(resolve => page.exposeFunction('getCanvasValue', resolve));
await page.evaluate(() => {
// The document canvas is where the plot points and lines are drawn.
// The only way to access the canvas is using document (using page.evaluate)
let data;
let canvas;
let ctx;
canvas = document.querySelector('.js-overlay canvas');
ctx = canvas.getContext('2d');
data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
const imageDataValues = Object.values(data);
let plotPixels = [];
// Each pixel consists of four values within the ImageData.data array. The for loop iterates by multiples of four.
// The values associated with each pixel are R (red), G (green), B (blue), and A (alpha), in that order.
for (let i = 0; i < imageDataValues.length;) {
if (imageDataValues[i] > 0) {
plotPixels.push({
startIndex: i,
endIndex: i + 3,
value: `rgb(${imageDataValues[i]}, ${imageDataValues[i + 1]}, ${imageDataValues[i + 2]}, ${imageDataValues[i + 3]})`
});
}
i = i + 4;
}
window.getCanvasValue(plotPixels.length);
});
return getTelemValuePromise;
}

View File

@ -32,14 +32,18 @@ class IndependentTimeContext extends TimeContext {
this.openmct = openmct;
this.unlisteners = [];
this.globalTimeContext = globalTimeContext;
this.upstreamTimeContext = undefined;
// We always start with the global time context.
// This upstream context will be undefined when an independent time context is added later.
this.upstreamTimeContext = this.globalTimeContext;
this.objectPath = objectPath;
this.refreshContext = this.refreshContext.bind(this);
this.resetContext = this.resetContext.bind(this);
this.removeIndependentContext = this.removeIndependentContext.bind(this);
this.refreshContext();
this.globalTimeContext.on('refreshContext', this.refreshContext);
this.globalTimeContext.on('removeOwnContext', this.removeIndependentContext);
}
bounds(newBounds) {
@ -202,10 +206,16 @@ class IndependentTimeContext extends TimeContext {
}
getUpstreamContext() {
// If a view has an independent context, don't return an upstream context
// Be aware that when a new independent time context is created, we assign the global context as default
if (this.hasOwnContext()) {
return undefined;
}
let timeContext = this.globalTimeContext;
this.objectPath.some((item, index) => {
const key = this.openmct.objects.makeKeyString(item.identifier);
//first index is the view object itself
// we're only interested in parents, not self, so index > 0
const itemContext = this.globalTimeContext.independentContexts.get(key);
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
//upstream time context
@ -219,6 +229,43 @@ class IndependentTimeContext extends TimeContext {
return timeContext;
}
/**
* Set the time context of a view to follow any upstream time contexts as necessary (defaulting to the global context)
* This needs to be separate from refreshContext
*/
removeIndependentContext(viewKey) {
const key = this.openmct.objects.makeKeyString(this.objectPath[0].identifier);
if (viewKey && key === viewKey) {
//this is necessary as the upstream context gets reassigned after this
this.stopFollowingTimeContext();
let timeContext = this.globalTimeContext;
this.objectPath.some((item, index) => {
const objectKey = this.openmct.objects.makeKeyString(item.identifier);
// we're only interested in any parents, not self, so index > 0
const itemContext = this.globalTimeContext.independentContexts.get(objectKey);
if (index > 0 && itemContext && itemContext.hasOwnContext()) {
//upstream time context
timeContext = itemContext;
return true;
}
return false;
});
this.upstreamTimeContext = timeContext;
this.followTimeContext();
// Emit bounds so that views that are changing context get the upstream bounds
this.emit('bounds', this.bounds());
// now that the view's context is set, tell others to check theirs in case they were following this view's context.
this.globalTimeContext.emit('refreshContext', viewKey);
}
}
}
export default IndependentTimeContext;

View File

@ -149,7 +149,7 @@ class TimeAPI extends GlobalTimeContext {
return () => {
//follow any upstream time context
this.emit('refreshContext');
this.emit('removeOwnContext', key);
};
}

View File

@ -117,22 +117,58 @@ describe("The Independent Time API", function () {
});
it("uses an object's independent time context if the parent doesn't have one", () => {
const domainObjectKey2 = `${domainObjectKey}-2`;
const domainObjectKey3 = `${domainObjectKey}-3`;
let timeContext = api.getContextForView([{
identifier: {
namespace: '',
key: domainObjectKey
}
}, {
}]);
let timeContext2 = api.getContextForView([{
identifier: {
namespace: '',
key: 'blah'
key: domainObjectKey2
}
}]);
let timeContext3 = api.getContextForView([{
identifier: {
namespace: '',
key: domainObjectKey3
}
}]);
// all bounds follow global time context
expect(timeContext.bounds()).toEqual(bounds);
expect(timeContext2.bounds()).toEqual(bounds);
expect(timeContext3.bounds()).toEqual(bounds);
// only first item has own context
let destroyTimeContext = api.addIndependentContext(domainObjectKey, independentBounds);
expect(timeContext.bounds()).toEqual(independentBounds);
expect(timeContext2.bounds()).toEqual(bounds);
expect(timeContext3.bounds()).toEqual(bounds);
// first and second item have own context
let destroyTimeContext2 = api.addIndependentContext(domainObjectKey2, independentBounds);
expect(timeContext.bounds()).toEqual(independentBounds);
expect(timeContext2.bounds()).toEqual(independentBounds);
expect(timeContext3.bounds()).toEqual(bounds);
// all items have own time context
let destroyTimeContext3 = api.addIndependentContext(domainObjectKey3, independentBounds);
expect(timeContext.bounds()).toEqual(independentBounds);
expect(timeContext2.bounds()).toEqual(independentBounds);
expect(timeContext3.bounds()).toEqual(independentBounds);
//remove own contexts one at a time - should revert to global time context
destroyTimeContext();
expect(timeContext.bounds()).toEqual(bounds);
expect(timeContext2.bounds()).toEqual(independentBounds);
expect(timeContext3.bounds()).toEqual(independentBounds);
destroyTimeContext2();
expect(timeContext.bounds()).toEqual(bounds);
expect(timeContext2.bounds()).toEqual(bounds);
expect(timeContext3.bounds()).toEqual(independentBounds);
destroyTimeContext3();
expect(timeContext.bounds()).toEqual(bounds);
expect(timeContext2.bounds()).toEqual(bounds);
expect(timeContext3.bounds()).toEqual(bounds);
});
it("Allows setting of valid bounds", function () {