From c089a4760d913fc3827d13d374c68058e4796321 Mon Sep 17 00:00:00 2001 From: Shefali Joshi Date: Tue, 3 May 2022 11:09:12 -0700 Subject: [PATCH] 2.0.3 merge to master (#5157) * Release 2.0.3 * Fix tick values for plots ticks in log mode and null check (#5119) * [2297] When there is no display range or range, skip setting the range value when auto scale is turned off. * If the formatted value is a number and a float, set precision to 2 decimal points. * Fix value assignment * Use whole numbers in log mode * Revert whole numbers fix - need floats for values between 0 and 1. * Handle scrolling to focused image on resize/new data (#5121) * Scroll to focused image when view resizes - this will force scrolling to focused image when going to/from view large mode * Scroll to the right if there is no paused focused image * [LAD Tables] Use Telemetry Collections (#5127) * Use telemetry collections to handle bounds checks * added telemetry collection to alphanumeric telemetry view (#5131) * Added animation styling for POS and CAM; adjusted cutoff for isNewImage (#5116) * Added animation styling for POS and CAM; adjusted cutoff for isNewImage * Remove animation from POS and CAM * Fix transactions overwriting latest objects with stale objects on save (#5132) * use object (map) instead of set to track dirty objects * fix tests due to internals change Co-authored-by: Nikhil * Gauge edit enabled 2.0.3 (#5133) * Gauge plugin #4896, add edit mode * Dynamic dial-type Gauge sizing by height and width (#5129) * Improve sizing strategy for gauges. * Do not install gauge by default for now Co-authored-by: Nikhil Co-authored-by: Jamie Vigliotta Co-authored-by: Andrew Henry * [Telemetry Collections] Include data with start and end bounds (#5145) * Reverts forced precision for log plots axis labels (#5147) * Condition Widgets trigger hundreds of persistence calls (#5146) Co-authored-by: unlikelyzero Co-authored-by: Jamie V Co-authored-by: Michael Rogers Co-authored-by: David Tsay <3614296+davetsay@users.noreply.github.com> Co-authored-by: Nikhil Co-authored-by: Charles Hacskaylo Co-authored-by: Andrew Henry Co-authored-by: unlikelyzero --- .../plugins/condition/condition.e2e.spec.js | 172 +++++++-- e2e/tests/recycled_storage.json | 22 ++ package.json | 2 +- src/MCT.js | 2 - src/api/objects/Transaction.js | 23 +- src/api/objects/TransactionSpec.js | 16 +- src/api/telemetry/TelemetryCollection.js | 4 +- src/plugins/LADTable/components/LADRow.vue | 65 +--- src/plugins/LADTable/pluginSpec.js | 17 +- .../components/ConditionWidget.vue | 104 ++++- .../components/TelemetryView.vue | 55 +-- src/plugins/gauge/GaugePluginSpec.js | 80 ++-- src/plugins/gauge/components/Gauge.vue | 361 +++++++++++------- .../gauge/components/GaugeFormController.vue | 1 + src/plugins/gauge/gauge-limit-util.js | 39 ++ src/plugins/gauge/gauge.scss | 124 ++---- .../imagery/components/ImageryView.vue | 17 +- .../imagery/components/imagery-view.scss | 7 +- src/plugins/plot/MctTicks.vue | 2 +- src/plugins/plot/configuration/YAxisModel.js | 4 + .../stackedPlot/mixins/objectStyles-mixin.js | 6 - src/plugins/plot/tickUtils.js | 5 - src/ui/components/ObjectView.vue | 7 - 23 files changed, 701 insertions(+), 434 deletions(-) create mode 100644 e2e/tests/recycled_storage.json create mode 100644 src/plugins/gauge/gauge-limit-util.js diff --git a/e2e/tests/plugins/condition/condition.e2e.spec.js b/e2e/tests/plugins/condition/condition.e2e.spec.js index 5443055c7c..225752548c 100644 --- a/e2e/tests/plugins/condition/condition.e2e.spec.js +++ b/e2e/tests/plugins/condition/condition.e2e.spec.js @@ -21,45 +21,163 @@ *****************************************************************************/ /* -This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. +This test suite is dedicated to tests which verify the basic operations surrounding conditionSets. Note: this +suite is sharing state between tests which is considered an anti-pattern. Implimenting in this way to +demonstrate some playwright for test developers. This pattern should not be re-used in other CRUD suites. */ const { test, expect } = require('@playwright/test'); -test.describe('Condition Set Operations', () => { - test('Create new button `condition set` creates new condition object', async ({ page }) => { - //Go to baseURL - await page.goto('/', { waitUntil: 'networkidle' }); +let conditionSetUrl; +let getConditionSetIdentifierFromUrl; - //Click the Create button - await page.click('button:has-text("Create")'); +test('Create new Condition Set object and store @localStorage', async ({ page, context }) => { + //Go to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); - // Click text=Condition Set - await page.click('text=Condition Set'); + //Click the Create button + await page.click('button:has-text("Create")'); - // Click text=OK + // Click text=Condition Set + await page.click('text=Condition Set'); + + // Click text=OK + await Promise.all([ + page.waitForNavigation(), + page.click('text=OK') + ]); + + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + //Save localStorage for future test execution + await context.storageState({ path: './e2e/tests/recycled_storage.json' }); + + //Set object identifier from url + conditionSetUrl = await page.url(); + console.log('conditionSetUrl ' + conditionSetUrl); + + getConditionSetIdentifierFromUrl = await conditionSetUrl.split('/').pop().split('?')[0]; + console.log('getConditionSetIdentifierFromUrl ' + getConditionSetIdentifierFromUrl); + +}); + +test.describe.serial('Condition Set CRUD Operations on @localStorage', () => { + //Load localStorage for subsequent tests + test.use({ storageState: './e2e/tests/recycled_storage.json' }); + + //Begin suite of tests again localStorage + test('Condition set object properties persist in main view and inspector', async ({ page }) => { + //Navigate to baseURL with injected localStorage + await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + + //Assertions on loaded Condition Set in main view + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + + //Assertions on loaded Condition Set in Inspector + await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy; + + //Reload Page await Promise.all([ - page.waitForNavigation(/*{ url: 'http://localhost:8080/#/browse/mine/dab945d4-5a84-480e-8180-222b4aa730fa?tc.mode=fixed&tc.startBound=1639696164435&tc.endBound=1639697964435&tc.timeSystem=utc&view=conditionSet.view' }*/), - page.click('text=OK') + page.reload(), + page.waitForLoadState('networkidle') ]); - await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + //Re-verify after reload + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + //Assertions on loaded Condition Set in Inspector + await expect.soft(page.locator('_vue=item.name=Unnamed Condition Set')).toBeTruthy; + }); - test.fixme('condition set object properties exist', async ({ page }) => { - //Go to object created in step one - //Verify the Condition Set properties persist on Save - //Verify the Condition Set properties persist on page.reload() - }); - test.fixme('condition set object can be modified', async ({ page }) => { - //Go to object created in step one + test('condition set object can be modified on @localStorage', async ({ page }) => { + await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + + //Assertions on loaded Condition Set in main view + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + //Update the Condition Set properties - //Verify the Condition Set properties persist on Save - //Verify the Condition Set properties persist on page.reload() + // Click Edit Button + await page.locator('text=Conditions View Snapshot >> button').nth(3).click(); + + //Edit Condition Set Name from main view + await page.locator('text=Unnamed Condition Set').first().fill('Renamed Condition Set'); + await page.locator('text=Renamed Condition Set').first().press('Enter'); + // Click Save Button + await page.locator('text=Snapshot Save and Finish Editing Save and Continue Editing >> button').nth(1).click(); + // Click Save and Finish Editing Option + await page.locator('text=Save and Finish Editing').click(); + + //Verify Main section reflects updated Name Property + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); + + // Verify Inspector properties + // Verify Inspector has updated Name property + await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); + // Verify Inspector Details has updated Name property + await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); + + // Verify Tree reflects updated Name proprety + // Expand Tree + await page.locator('text=Open MCT My Items >> span >> nth=3').click(); + // Verify Condition Set Object is renamed in Tree + await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + // Verify Search Tree reflects renamed Name property + await page.locator('input[type="search"]').fill('Renamed'); + await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + + //Reload Page + await Promise.all([ + page.reload(), + page.waitForLoadState('networkidle') + ]); + + //Verify Main section reflects updated Name Property + await expect.soft(page.locator('.l-browse-bar__object-name')).toContainText('Renamed Condition Set'); + + // Verify Inspector properties + // Verify Inspector has updated Name property + await expect.soft(page.locator('text=Renamed Condition Set').nth(1)).toBeTruthy(); + // Verify Inspector Details has updated Name property + await expect.soft(page.locator('text=Renamed Condition Set').nth(2)).toBeTruthy(); + + // Verify Tree reflects updated Name proprety + // Expand Tree + await page.locator('text=Open MCT My Items >> span >> nth=3').click(); + // Verify Condition Set Object is renamed in Tree + await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); + // Verify Search Tree reflects renamed Name property + await page.locator('input[type="search"]').fill('Renamed'); + await expect(page.locator('a:has-text("Renamed Condition Set")')).toBeTruthy(); }); - test.fixme('condition set object can be deleted', async ({ page }) => { - //Go to object created in step one - //Verify that Condition Set object can be deleted - //Verify the Condition Set object does not exist in Tree - //Verify the Condition Set object does not exist with direct navigation to object's URL + test('condition set object can be deleted by Search Tree Actions menu on @localStorage', async ({ page }) => { + //Navigate to baseURL + await page.goto('/', { waitUntil: 'networkidle' }); + + //Expect Unnamed Condition Set to be visible in Main View + await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set")')).toBeVisible(); + + // Search for Unnamed Condition Set + await page.locator('input[type="search"]').fill('Unnamed Condition Set'); + // Right Click to Open Actions Menu + await page.locator('a:has-text("Unnamed Condition Set")').click({ + button: 'right' + }); + // Click Remove Action + await page.locator('text=Remove').click(); + + await page.locator('text=OK').click(); + + //Expect Unnamed Condition Set to be removed in Main View + await expect(page.locator('a:has-text("Unnamed Condition Set Condition Set")')).not.toBeVisible(); + + await page.locator('.c-search__clear-input').click(); + // Search for Unnamed Condition Set + await page.locator('input[type="search"]').fill('Unnamed Condition Set'); + // Expect Unnamed Condition Set to be removed + await expect(page.locator('a:has-text("Unnamed Condition Set")')).not.toBeVisible(); + + //Feature? + //Domain Object is still available by direct URL after delete + await page.goto(conditionSetUrl, { waitUntil: 'networkidle' }); + await expect(page.locator('.l-browse-bar__object-name')).toContainText('Unnamed Condition Set'); + }); }); diff --git a/e2e/tests/recycled_storage.json b/e2e/tests/recycled_storage.json new file mode 100644 index 0000000000..86c3f906bc --- /dev/null +++ b/e2e/tests/recycled_storage.json @@ -0,0 +1,22 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "tcHistory", + "value": "{\"utc\":[{\"start\":1651513945533,\"end\":1651515745533}]}" + }, + { + "name": "mct", + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"persisted\":1651515746374,\"modified\":1651515746374},\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\":{\"name\":\"Unnamed Condition Set\",\"type\":\"conditionSet\",\"identifier\":{\"key\":\"c0f99e39-85e7-4ef7-99b1-ef52d4ed69b2\",\"namespace\":\"\"},\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"e35a066b-eb0e-4b05-a4c9-cc31dc202572\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[],\"telemetry\":{},\"modified\":1651515746373,\"location\":\"mine\",\"persisted\":1651515746373}}" + }, + { + "name": "mct-tree-expanded", + "value": "[]" + } + ] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index ae48d44db4..4d779fb422 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct", - "version": "2.0.3-SNAPSHOT", + "version": "2.0.3", "description": "The Open MCT core platform", "devDependencies": { "@babel/eslint-parser": "7.16.3", diff --git a/src/MCT.js b/src/MCT.js index 19700d691f..00624460d5 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -241,8 +241,6 @@ define([ this.branding = BrandingAPI.default; // Plugins that are installed by default - - this.install(this.plugins.Gauge()); this.install(this.plugins.Plot()); this.install(this.plugins.Chart()); this.install(this.plugins.TelemetryTable.default()); diff --git a/src/api/objects/Transaction.js b/src/api/objects/Transaction.js index 65ffe1754c..d1c200f7d7 100644 --- a/src/api/objects/Transaction.js +++ b/src/api/objects/Transaction.js @@ -22,12 +22,14 @@ export default class Transaction { constructor(objectAPI) { - this.dirtyObjects = new Set(); + this.dirtyObjects = {}; this.objectAPI = objectAPI; } add(object) { - this.dirtyObjects.add(object); + const key = this.objectAPI.makeKeyString(object.identifier); + + this.dirtyObjects[key] = object; } cancel() { @@ -37,7 +39,8 @@ export default class Transaction { commit() { const promiseArray = []; const save = this.objectAPI.save.bind(this.objectAPI); - this.dirtyObjects.forEach(object => { + + Object.values(this.dirtyObjects).forEach(object => { promiseArray.push(this.createDirtyObjectPromise(object, save)); }); @@ -48,7 +51,9 @@ export default class Transaction { return new Promise((resolve, reject) => { action(object) .then((success) => { - this.dirtyObjects.delete(object); + const key = this.objectAPI.makeKeyString(object.identifier); + + delete this.dirtyObjects[key]; resolve(success); }) .catch(reject); @@ -57,7 +62,8 @@ export default class Transaction { getDirtyObject(identifier) { let dirtyObject; - this.dirtyObjects.forEach(object => { + + Object.values(this.dirtyObjects).forEach(object => { const areIdsEqual = this.objectAPI.areIdsEqual(object.identifier, identifier); if (areIdsEqual) { dirtyObject = object; @@ -67,14 +73,11 @@ export default class Transaction { return dirtyObject; } - start() { - this.dirtyObjects = new Set(); - } - _clear() { const promiseArray = []; const refresh = this.objectAPI.refresh.bind(this.objectAPI); - this.dirtyObjects.forEach(object => { + + Object.values(this.dirtyObjects).forEach(object => { promiseArray.push(this.createDirtyObjectPromise(object, refresh)); }); diff --git a/src/api/objects/TransactionSpec.js b/src/api/objects/TransactionSpec.js index 286401ad18..8d195f0fe2 100644 --- a/src/api/objects/TransactionSpec.js +++ b/src/api/objects/TransactionSpec.js @@ -34,24 +34,24 @@ describe("Transaction Class", () => { }); it('has no dirty objects', () => { - expect(transaction.dirtyObjects.size).toEqual(0); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); }); it('add(), adds object to dirtyObjects', () => { const mockDomainObjects = createMockDomainObjects(); transaction.add(mockDomainObjects[0]); - expect(transaction.dirtyObjects.size).toEqual(1); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(1); }); it('cancel(), clears all dirtyObjects', (done) => { const mockDomainObjects = createMockDomainObjects(3); mockDomainObjects.forEach(transaction.add.bind(transaction)); - expect(transaction.dirtyObjects.size).toEqual(3); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(3); transaction.cancel() .then(success => { - expect(transaction.dirtyObjects.size).toEqual(0); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); }).finally(done); }); @@ -59,12 +59,12 @@ describe("Transaction Class", () => { const mockDomainObjects = createMockDomainObjects(3); mockDomainObjects.forEach(transaction.add.bind(transaction)); - expect(transaction.dirtyObjects.size).toEqual(3); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(3); spyOn(objectAPI, 'save').and.callThrough(); transaction.commit() .then(success => { - expect(transaction.dirtyObjects.size).toEqual(0); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); expect(objectAPI.save.calls.count()).toEqual(3); }).finally(done); }); @@ -73,7 +73,7 @@ describe("Transaction Class", () => { const mockDomainObjects = createMockDomainObjects(); transaction.add(mockDomainObjects[0]); - expect(transaction.dirtyObjects.size).toEqual(1); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(1); const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier); expect(dirtyObject).toEqual(mockDomainObjects[0]); @@ -82,7 +82,7 @@ describe("Transaction Class", () => { it('getDirtyObject(), returns empty dirtyObject for no active transaction', () => { const mockDomainObjects = createMockDomainObjects(); - expect(transaction.dirtyObjects.size).toEqual(0); + expect(Object.keys(transaction.dirtyObjects).length).toEqual(0); const dirtyObject = transaction.getDirtyObject(mockDomainObjects[0].identifier); expect(dirtyObject).toEqual(undefined); diff --git a/src/api/telemetry/TelemetryCollection.js b/src/api/telemetry/TelemetryCollection.js index 99a06b28cb..4386a4729e 100644 --- a/src/api/telemetry/TelemetryCollection.js +++ b/src/api/telemetry/TelemetryCollection.js @@ -185,8 +185,8 @@ export class TelemetryCollection extends EventEmitter { for (let datum of data) { parsedValue = this.parseTime(datum); - beforeStartOfBounds = parsedValue <= this.lastBounds.start; - afterEndOfBounds = parsedValue >= this.lastBounds.end; + beforeStartOfBounds = parsedValue < this.lastBounds.start; + afterEndOfBounds = parsedValue > this.lastBounds.end; if (!afterEndOfBounds && !beforeStartOfBounds) { let isDuplicate = false; diff --git a/src/plugins/LADTable/components/LADRow.vue b/src/plugins/LADTable/components/LADRow.vue index 8e0ae85097..e18f1f7533 100644 --- a/src/plugins/LADTable/components/LADRow.vue +++ b/src/plugins/LADTable/components/LADRow.vue @@ -1,4 +1,3 @@ - /***************************************************************************** * Open MCT, Copyright (c) 2014-2022, United States Government * as represented by the Administrator of the National Aeronautics and Space @@ -114,14 +113,12 @@ export default { this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.formats = this.openmct.telemetry.getFormatMap(this.metadata); this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); - this.bounds = this.openmct.time.bounds(); this.limitEvaluator = this.openmct .telemetry .limitEvaluator(this.domainObject); this.openmct.time.on('timeSystem', this.updateTimeSystem); - this.openmct.time.on('bounds', this.updateBounds); this.timestampKey = this.openmct.time.timeSystem().key; @@ -135,72 +132,41 @@ export default { this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined; - this.unsubscribe = this.openmct - .telemetry - .subscribe(this.domainObject, this.setLatestValues); - - this.requestHistory(); + this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, { + size: 1, + strategy: 'latest' + }); + this.telemetryCollection.on('add', this.setLatestValues); + this.telemetryCollection.on('clear', this.resetValues); + this.telemetryCollection.load(); if (this.hasUnits) { this.setUnit(); } }, destroyed() { - this.unsubscribe(); this.openmct.time.off('timeSystem', this.updateTimeSystem); - this.openmct.time.off('bounds', this.updateBounds); + this.telemetryCollection.off('add', this.setLatestValues); + this.telemetryCollection.off('clear', this.resetValues); + + this.telemetryCollection.destroy(); }, methods: { updateView() { if (!this.updatingView) { this.updatingView = true; requestAnimationFrame(() => { - let newTimestamp = this.getParsedTimestamp(this.latestDatum); - - if (this.shouldUpdate(newTimestamp)) { - this.timestamp = newTimestamp; - this.datum = this.latestDatum; - } - + this.timestamp = this.getParsedTimestamp(this.latestDatum); + this.datum = this.latestDatum; this.updatingView = false; }); } }, - setLatestValues(datum) { - this.latestDatum = datum; - + setLatestValues(data) { + this.latestDatum = data[data.length - 1]; this.updateView(); }, - shouldUpdate(newTimestamp) { - return this.inBounds(newTimestamp) - && (this.timestamp === undefined || newTimestamp > this.timestamp); - }, - requestHistory() { - this.openmct - .telemetry - .request(this.domainObject, { - start: this.bounds.start, - end: this.bounds.end, - size: 1, - strategy: 'latest' - }) - .then((array) => this.setLatestValues(array[array.length - 1])) - .catch((error) => { - console.warn('Error fetching data', error); - }); - }, - updateBounds(bounds, isTick) { - this.bounds = bounds; - if (!isTick) { - this.resetValues(); - this.requestHistory(); - } - }, - inBounds(timestamp) { - return timestamp >= this.bounds.start && timestamp <= this.bounds.end; - }, updateTimeSystem(timeSystem) { - this.resetValues(); this.timestampKey = timeSystem.key; }, updateViewContext() { @@ -241,4 +207,3 @@ export default { } }; - diff --git a/src/plugins/LADTable/pluginSpec.js b/src/plugins/LADTable/pluginSpec.js index bb88c35d13..33985983e4 100644 --- a/src/plugins/LADTable/pluginSpec.js +++ b/src/plugins/LADTable/pluginSpec.js @@ -46,6 +46,7 @@ describe("The LAD Table", () => { let openmct; let ladPlugin; + let historicalProvider; let parent; let child; let telemetryCount = 3; @@ -81,6 +82,13 @@ describe("The LAD Table", () => { spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({})); + historicalProvider = { + request: () => { + return Promise.resolve([]); + } + }; + spyOn(openmct.telemetry, 'findRequestProvider').and.returnValue(historicalProvider); + openmct.time.bounds({ start: bounds.start, end: bounds.end @@ -147,7 +155,7 @@ describe("The LAD Table", () => { // add another telemetry object as composition in lad table to test multi rows mockObj.ladTable.composition.push(anotherTelemetryObj.identifier); - beforeEach(async () => { + beforeEach(async (done) => { let telemetryRequestResolve; let telemetryObjectResolve; let anotherTelemetryObjectResolve; @@ -166,11 +174,12 @@ describe("The LAD Table", () => { callBack(); }); - openmct.telemetry.request.and.callFake(() => { + historicalProvider.request = () => { telemetryRequestResolve(mockTelemetry); return telemetryRequestPromise; - }); + }; + openmct.objects.get.and.callFake((obj) => { if (obj.key === 'telemetry-object') { telemetryObjectResolve(mockObj.telemetry); @@ -195,6 +204,8 @@ describe("The LAD Table", () => { await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]); await Vue.nextTick(); + + done(); }); it("should show one row per object in the composition", () => { diff --git a/src/plugins/conditionWidget/components/ConditionWidget.vue b/src/plugins/conditionWidget/components/ConditionWidget.vue index 2f2ec47245..80220dd754 100644 --- a/src/plugins/conditionWidget/components/ConditionWidget.vue +++ b/src/plugins/conditionWidget/components/ConditionWidget.vue @@ -27,7 +27,7 @@ :href="url" >
- {{ internalDomainObject.conditionalLabel || internalDomainObject.label }} + {{ label }}
@@ -39,28 +39,112 @@ export default { inject: ['openmct', 'domainObject'], data: function () { return { - internalDomainObject: this.domainObject + conditionalLabel: '', + conditionSetIdentifier: null, + domainObjectLabel: '', + url: null, + urlDefined: false, + useConditionSetOutputAsLabel: false }; }, computed: { - urlDefined() { - return this.internalDomainObject.url && this.internalDomainObject.url.length > 0; - }, - url() { - return this.urlDefined ? sanitizeUrl(this.internalDomainObject.url) : null; + label() { + return this.useConditionSetOutputAsLabel + ? this.conditionalLabel + : this.domainObjectLabel + ; + } + }, + watch: { + conditionSetIdentifier: { + handler(newValue, oldValue) { + if (!oldValue || !newValue || !this.openmct.objects.areIdsEqual(newValue, oldValue)) { + return; + } + + this.listenToConditionSetChanges(); + }, + deep: true } }, mounted() { - this.unlisten = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject); + this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject); + + if (this.domainObject) { + this.updateDomainObject(this.domainObject); + this.listenToConditionSetChanges(); + } }, beforeDestroy() { + this.conditionSetIdentifier = null; + if (this.unlisten) { this.unlisten(); } + + this.stopListeningToConditionSetChanges(); }, methods: { - updateInternalDomainObject(domainObject) { - this.internalDomainObject = domainObject; + async listenToConditionSetChanges() { + if (!this.conditionSetIdentifier) { + return; + } + + const conditionSetDomainObject = await this.openmct.objects.get(this.conditionSetIdentifier); + this.stopListeningToConditionSetChanges(); + + if (!conditionSetDomainObject) { + this.openmct.notifications.alert('Unable to find condition set'); + } + + this.telemetryCollection = this.openmct.telemetry.requestCollection(conditionSetDomainObject, { + size: 1, + strategy: 'latest' + }); + + this.telemetryCollection.on('add', this.updateConditionLabel, this); + this.telemetryCollection.load(); + }, + stopListeningToConditionSetChanges() { + if (this.telemetryCollection) { + this.telemetryCollection.off('add', this.updateConditionLabel, this); + this.telemetryCollection.destroy(); + this.telemetryCollection = null; + } + }, + updateConditionLabel([latestDatum]) { + if (!this.conditionSetIdentifier) { + this.stopListeningToConditionSetChanges(); + + return; + } + + this.conditionalLabel = latestDatum.output || ''; + }, + updateDomainObject(domainObject) { + if (this.domainObjectLabel !== domainObject.label) { + this.domainObjectLabel = domainObject.label; + } + + const urlDefined = domainObject.url && domainObject.url.length > 0; + if (this.urlDefined !== urlDefined) { + this.urlDefined = urlDefined; + } + + const url = this.urlDefined ? sanitizeUrl(domainObject.url) : null; + if (this.url !== url) { + this.url = url; + } + + const conditionSetIdentifier = domainObject.configuration.objectStyles.conditionSetIdentifier; + if (this.conditionSetIdentifier !== conditionSetIdentifier) { + this.conditionSetIdentifier = conditionSetIdentifier; + } + + const useConditionSetOutputAsLabel = this.conditionSetIdentifier && domainObject.configuration.useConditionSetOutputAsLabel; + if (this.useConditionSetOutputAsLabel !== useConditionSetOutputAsLabel) { + this.useConditionSetOutputAsLabel = useConditionSetOutputAsLabel; + } } } }; diff --git a/src/plugins/displayLayout/components/TelemetryView.vue b/src/plugins/displayLayout/components/TelemetryView.vue index a2ff8b3bc8..3a759db9df 100644 --- a/src/plugins/displayLayout/components/TelemetryView.vue +++ b/src/plugins/displayLayout/components/TelemetryView.vue @@ -222,20 +222,20 @@ export default { .then(this.setObject); } - this.openmct.time.on("bounds", this.refreshData); - this.status = this.openmct.status.get(this.item.identifier); this.removeStatusListener = this.openmct.status.observe(this.item.identifier, this.setStatus); }, beforeDestroy() { - this.removeSubscription(); this.removeStatusListener(); if (this.removeSelectable) { this.removeSelectable(); } - this.openmct.time.off("bounds", this.refreshData); + this.telemetryCollection.off('add', this.setLatestValues); + this.telemetryCollection.off('clear', this.refreshData); + + this.telemetryCollection.destroy(); if (this.mutablePromise) { this.mutablePromise.then(() => { @@ -253,34 +253,9 @@ export default { return `At ${timeFormatter.format(this.datum)} ${this.domainObject.name} had a value of ${this.telemetryValue}${unit}`; }, - requestHistoricalData() { - let bounds = this.openmct.time.bounds(); - let options = { - start: bounds.start, - end: bounds.end, - size: 1, - strategy: 'latest' - }; - this.openmct.telemetry.request(this.domainObject, options) - .then(data => { - if (data.length > 0) { - this.latestDatum = data[data.length - 1]; - this.updateView(); - } - }); - }, - subscribeToObject() { - this.subscription = this.openmct.telemetry.subscribe(this.domainObject, function (datum) { - const key = this.openmct.time.timeSystem().key; - const datumTimeStamp = datum[key]; - if (this.openmct.time.clock() !== undefined - || (datumTimeStamp - && (this.openmct.time.bounds().end >= datumTimeStamp)) - ) { - this.latestDatum = datum; - this.updateView(); - } - }.bind(this)); + setLatestValues(data) { + this.latestDatum = data[data.length - 1]; + this.updateView(); }, updateView() { if (!this.updatingView) { @@ -291,17 +266,10 @@ export default { }); } }, - removeSubscription() { - if (this.subscription) { - this.subscription(); - this.subscription = undefined; - } - }, refreshData(bounds, isTick) { if (!isTick) { this.latestDatum = undefined; this.updateView(); - this.requestHistoricalData(this.domainObject); } }, setObject(domainObject) { @@ -315,8 +283,13 @@ export default { const valueMetadata = this.metadata.value(this.item.value); this.customStringformatter = this.openmct.telemetry.customStringFormatter(valueMetadata, this.item.format); - this.requestHistoricalData(); - this.subscribeToObject(); + this.telemetryCollection = this.openmct.telemetry.requestCollection(this.domainObject, { + size: 1, + strategy: 'latest' + }); + this.telemetryCollection.on('add', this.setLatestValues); + this.telemetryCollection.on('clear', this.refreshData); + this.telemetryCollection.load(); this.currentObjectPath = this.objectPath.slice(); this.currentObjectPath.unshift(this.domainObject); diff --git a/src/plugins/gauge/GaugePluginSpec.js b/src/plugins/gauge/GaugePluginSpec.js index 982cb58c49..5894498063 100644 --- a/src/plugins/gauge/GaugePluginSpec.js +++ b/src/plugins/gauge/GaugePluginSpec.js @@ -53,6 +53,8 @@ describe('Gauge plugin', () => { openmct = createOpenMct(); openmct.on('start', done); + openmct.install(openmct.plugins.Gauge()); + openmct.startHeadless(); }); @@ -190,28 +192,27 @@ describe('Gauge plugin', () => { }); it('renders gauge element', () => { - const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge'); + const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper'); expect(gaugeElement.length).toBe(1); }); it('renders major elements', () => { - const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper'); - const rangeElement = gaugeHolder.querySelector('.c-gauge__range'); - const curveElement = gaugeHolder.querySelector('.c-gauge__curval'); - const dialElement = gaugeHolder.querySelector('.c-dial'); + const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); + const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range'); + const valueElement = gaugeHolder.querySelector('.js-dial-current-value'); - const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement); + const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); expect(hasMajorElements).toBe(true); }); it('renders correct min max values', () => { - expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`); + expect(gaugeHolder.querySelector('.js-gauge-dial-range').textContent).toEqual(`${minValue} ${maxValue}`); }); it('renders correct current value', (done) => { function WatchUpdateValue() { - const textElement = gaugeHolder.querySelector('.c-gauge__curval-text'); + const textElement = gaugeHolder.querySelector('.js-dial-current-value'); expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision)); done(); } @@ -326,28 +327,27 @@ describe('Gauge plugin', () => { }); it('renders gauge element', () => { - const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge'); + const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper'); expect(gaugeElement.length).toBe(1); }); it('renders major elements', () => { - const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper'); - const rangeElement = gaugeHolder.querySelector('.c-gauge__range'); - const curveElement = gaugeHolder.querySelector('.c-gauge__curval'); - const dialElement = gaugeHolder.querySelector('.c-dial'); + const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); + const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range'); + const valueElement = gaugeHolder.querySelector('.js-dial-current-value'); - const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement); + const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); expect(hasMajorElements).toBe(true); }); it('renders correct min max values', () => { - expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${minValue} ${maxValue}`); + expect(gaugeHolder.querySelector('.js-gauge-dial-range').textContent).toEqual(`${minValue} ${maxValue}`); }); it('renders correct current value', (done) => { function WatchUpdateValue() { - const textElement = gaugeHolder.querySelector('.c-gauge__curval-text'); + const textElement = gaugeHolder.querySelector('.js-dial-current-value'); expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision)); done(); } @@ -462,28 +462,27 @@ describe('Gauge plugin', () => { }); it('renders gauge element', () => { - const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge'); + const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper'); expect(gaugeElement.length).toBe(1); }); it('renders major elements', () => { - const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper'); - const rangeElement = gaugeHolder.querySelector('.c-gauge__range'); - const curveElement = gaugeHolder.querySelector('.c-meter'); - const dialElement = gaugeHolder.querySelector('.c-meter__bg'); + const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); + const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range'); + const valueElement = gaugeHolder.querySelector('.js-meter-current-value'); - const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement); + const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); expect(hasMajorElements).toBe(true); }); it('renders correct min max values', () => { - expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${maxValue} ${minValue}`); + expect(gaugeHolder.querySelector('.js-gauge-meter-range').textContent).toEqual(`${maxValue} ${minValue}`); }); it('renders correct current value', (done) => { function WatchUpdateValue() { - const textElement = gaugeHolder.querySelector('.c-gauge__curval-text'); + const textElement = gaugeHolder.querySelector('.js-meter-current-value'); expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision)); done(); } @@ -560,17 +559,16 @@ describe('Gauge plugin', () => { }); it('renders gauge element', () => { - const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge'); + const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper'); expect(gaugeElement.length).toBe(1); }); it('renders major elements', () => { - const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper'); - const rangeElement = gaugeHolder.querySelector('.c-gauge__range'); - const curveElement = gaugeHolder.querySelector('.c-meter'); - const dialElement = gaugeHolder.querySelector('.c-meter__bg'); + const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); + const rangeElement = gaugeHolder.querySelector('.js-gauge-meter-range'); + const valueElement = gaugeHolder.querySelector('.js-meter-current-value'); - const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement); + const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); expect(hasMajorElements).toBe(true); }); @@ -643,17 +641,16 @@ describe('Gauge plugin', () => { }); it('renders gauge element', () => { - const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge'); + const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper'); expect(gaugeElement.length).toBe(1); }); it('renders major elements', () => { - const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper'); + const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); const rangeElement = gaugeHolder.querySelector('.c-gauge__range'); const curveElement = gaugeHolder.querySelector('.c-meter'); - const dialElement = gaugeHolder.querySelector('.c-meter__bg'); - const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement); + const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement); expect(hasMajorElements).toBe(true); }); @@ -772,28 +769,27 @@ describe('Gauge plugin', () => { }); it('renders gauge element', () => { - const gaugeElement = gaugeHolder.querySelectorAll('.c-gauge'); + const gaugeElement = gaugeHolder.querySelectorAll('.js-gauge-wrapper'); expect(gaugeElement.length).toBe(1); }); it('renders major elements', () => { - const wrapperElement = gaugeHolder.querySelector('.c-gauge__wrapper'); - const rangeElement = gaugeHolder.querySelector('.c-gauge__range'); - const curveElement = gaugeHolder.querySelector('.c-gauge__curval'); - const dialElement = gaugeHolder.querySelector('.c-dial'); + const wrapperElement = gaugeHolder.querySelector('.js-gauge-wrapper'); + const rangeElement = gaugeHolder.querySelector('.js-gauge-dial-range'); + const valueElement = gaugeHolder.querySelector('.js-dial-current-value'); - const hasMajorElements = Boolean(wrapperElement && rangeElement && curveElement && dialElement); + const hasMajorElements = Boolean(wrapperElement && rangeElement && valueElement); expect(hasMajorElements).toBe(true); }); it('renders correct min max values', () => { - expect(gaugeHolder.querySelector('.c-gauge__range').textContent).toEqual(`${gaugeViewObject.configuration.gaugeController.min} ${gaugeViewObject.configuration.gaugeController.max}`); + expect(gaugeHolder.querySelector('.js-gauge-dial-range').textContent).toEqual(`${gaugeViewObject.configuration.gaugeController.min} ${gaugeViewObject.configuration.gaugeController.max}`); }); it('renders correct current value', (done) => { function WatchUpdateValue() { - const textElement = gaugeHolder.querySelector('.c-gauge__curval-text'); + const textElement = gaugeHolder.querySelector('.js-dial-current-value'); expect(Number(textElement.textContent).toFixed(gaugeViewObject.configuration.gaugeController.precision)).toBe(randomValue.toFixed(gaugeViewObject.configuration.gaugeController.precision)); done(); } diff --git a/src/plugins/gauge/components/Gauge.vue b/src/plugins/gauge/components/Gauge.vue index 8c6d335156..91036ce965 100644 --- a/src/plugins/gauge/components/Gauge.vue +++ b/src/plugins/gauge/components/Gauge.vue @@ -21,168 +21,259 @@ *****************************************************************************/ -