diff --git a/e2e/test-data/condition_set_storage.json b/e2e/test-data/condition_set_storage.json new file mode 100644 index 0000000000..8fa8cd0c8d --- /dev/null +++ b/e2e/test-data/condition_set_storage.json @@ -0,0 +1,22 @@ +{ + "cookies": [], + "origins": [ + { + "origin": "http://localhost:8080", + "localStorage": [ + { + "name": "mct", + "value": "{\"mine\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1725480977300,\"created\":1725480975674,\"persisted\":1725480977301},\"954af939-eaf8-4977-8cee-57f36b58aae3\":{\"identifier\":{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"},\"name\":\"Test Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"id\":\"1f4b8d87-297b-4a2a-a2d2-46c42eb41b39\",\"configuration\":{\"name\":\"Test Condition\",\"output\":\"Test Condition Met\",\"trigger\":\"all\",\"criteria\":[{\"id\":\"034b4dfe-b17e-43f0-9787-93e4666d2690\",\"telemetry\":{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"},\"operation\":\"greaterThan\",\"input\":[0],\"metadata\":\"sin\"}]},\"summary\":\"Match if all criteria are met: VIPER Rover Heading Sine > 0 \"},{\"isDefault\":true,\"id\":\"c56ff651-547e-4704-a8b7-4f01247e2fa7\",\"configuration\":{\"name\":\"Default\",\"output\":\"Test Condition Unmet\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"\"}]},\"composition\":[{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"}],\"telemetry\":{},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Conditional Styling Data @localStorage @generatedata\\nGenerate basic condition set\\nchrome\",\"modified\":1725480978924,\"location\":\"mine\",\"created\":1725480977299,\"persisted\":1725480978924},\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\":{\"identifier\":{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":5,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1725480978545,\"location\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"created\":1725480977993,\"persisted\":1725480978545}}" + }, + { + "name": "mct-tree-expanded", + "value": "[]" + }, + { + "name": "mct-recent-objects", + "value": "[{\"objectPath\":[{\"identifier\":{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"},\"name\":\"Test Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"c56ff651-547e-4704-a8b7-4f01247e2fa7\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"}],\"telemetry\":{},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Conditional Styling Data @localStorage @generatedata\\nGenerate basic condition set\\nchrome\",\"modified\":1725480977994,\"location\":\"mine\",\"created\":1725480977299,\"persisted\":1725480977994},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1725480977300,\"created\":1725480975674,\"persisted\":1725480977301},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/954af939-eaf8-4977-8cee-57f36b58aae3\",\"domainObject\":{\"identifier\":{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"},\"name\":\"Test Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"c56ff651-547e-4704-a8b7-4f01247e2fa7\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"}],\"telemetry\":{},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Conditional Styling Data @localStorage @generatedata\\nGenerate basic condition set\\nchrome\",\"modified\":1725480977994,\"location\":\"mine\",\"created\":1725480977299,\"persisted\":1725480977994}},{\"objectPath\":[{\"identifier\":{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":5,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1725480978545,\"location\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"created\":1725480977993,\"persisted\":1725480978545},{\"identifier\":{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"},\"name\":\"Test Condition Set\",\"type\":\"conditionSet\",\"configuration\":{\"conditionTestData\":[],\"conditionCollection\":[{\"isDefault\":true,\"id\":\"c56ff651-547e-4704-a8b7-4f01247e2fa7\",\"configuration\":{\"name\":\"Default\",\"output\":\"Default\",\"trigger\":\"all\",\"criteria\":[]},\"summary\":\"Default condition\"}]},\"composition\":[{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"}],\"telemetry\":{},\"notes\":\"framework/generateLocalStorageData.e2e.spec.js\\nGenerate Conditional Styling Data @localStorage @generatedata\\nGenerate basic condition set\\nchrome\",\"modified\":1725480977994,\"location\":\"mine\",\"created\":1725480977299,\"persisted\":1725480977994},{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1725480977300,\"created\":1725480975674,\"persisted\":1725480977301},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine/954af939-eaf8-4977-8cee-57f36b58aae3/1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"domainObject\":{\"identifier\":{\"key\":\"1eafa8cc-092f-4a5f-9206-9e5d8a070ea1\",\"namespace\":\"\"},\"name\":\"VIPER Rover Heading\",\"type\":\"generator\",\"telemetry\":{\"period\":10,\"amplitude\":1,\"offset\":0,\"dataRateInHz\":1,\"phase\":0,\"randomness\":0,\"loadDelay\":0,\"infinityValues\":false,\"exceedFloat32\":false,\"staleness\":false},\"modified\":1725480978542,\"location\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"created\":1725480977993,\"persisted\":1725480977993}},{\"objectPath\":[{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1725480977300,\"created\":1725480975674,\"persisted\":1725480977301},{\"identifier\":{\"key\":\"ROOT\",\"namespace\":\"\"},\"name\":\"Open MCT\",\"type\":\"root\",\"composition\":[{\"key\":\"mine\",\"namespace\":\"\"}]}],\"navigationPath\":\"/browse/mine\",\"domainObject\":{\"identifier\":{\"key\":\"mine\",\"namespace\":\"\"},\"name\":\"My Items\",\"type\":\"folder\",\"composition\":[{\"key\":\"954af939-eaf8-4977-8cee-57f36b58aae3\",\"namespace\":\"\"}],\"location\":\"ROOT\",\"modified\":1725480977300,\"created\":1725480975674,\"persisted\":1725480977301}}]" + } + ] + } + ] +} \ No newline at end of file diff --git a/e2e/tests/framework/generateLocalStorageData.e2e.spec.js b/e2e/tests/framework/generateLocalStorageData.e2e.spec.js index 544a04e25f..822c51ebb1 100644 --- a/e2e/tests/framework/generateLocalStorageData.e2e.spec.js +++ b/e2e/tests/framework/generateLocalStorageData.e2e.spec.js @@ -286,6 +286,55 @@ test.describe('Generate Visual Test Data @localStorage @generatedata @clock', () }); }); +test.describe('Generate Conditional Styling Data @localStorage @generatedata', () => { + test('Generate basic condition set', async ({ page, context }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + // Create a Condition Set + const conditionSet = await createDomainObjectWithDefaults(page, { + type: 'Condition Set', + name: 'Test Condition Set' + }); + + // Create a Telemetry Object (Sine Wave Generator) + const swg = await createExampleTelemetryObject(page, conditionSet.uuid); + + // Edit the Telemetry Object to have a 10hz data rate (Gotta go fast!) + await page.goto(swg.url); + await page.getByLabel('More actions').click(); + await page.getByRole('menuitem', { name: 'Edit Properties...' }).click(); + await page.getByLabel('Period', { exact: true }).fill('5'); + await page.getByLabel('Save').click(); + + // Edit the Condition Set + await page.goto(conditionSet.url); + await page.getByLabel('Edit Object').click(); + + // Add a Condition to the Condition Set + await page.getByLabel('Add Condition').click(); + await page.getByLabel('Condition Name Input').first().fill('Test Condition'); + await page.getByLabel('Condition Output Type').first().selectOption('String'); + await page.getByLabel('Condition Output String').first().fill('Test Condition Met'); + + // Condition: True if sine value > 0 (half the time) + await page.getByLabel('Criterion Telemetry Selection').selectOption(swg.name); + await page.getByLabel('Criterion Metadata Selection').selectOption('Sine'); + await page.getByLabel('Criterion Comparison Selection').selectOption('is greater than'); + await page.getByLabel('Criterion Input').first().fill('0'); + + // Rename default condition + await page.getByLabel('Condition Output String').nth(1).fill('Test Condition Unmet'); + await page.getByLabel('Save').click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // Save localStorage for future test execution + await context.storageState({ + path: fileURLToPath( + new URL('../../../e2e/test-data/condition_set_storage.json', import.meta.url) + ) + }); + }); +}); + test.describe('Validate Overlay Plot with Telemetry Object @localStorage @generatedata', () => { test.use({ storageState: fileURLToPath( diff --git a/e2e/tests/functional/plugins/styling/conditional/displayLayoutConditionalStyling.e2e.spec.js b/e2e/tests/functional/plugins/styling/conditional/displayLayoutConditionalStyling.e2e.spec.js new file mode 100644 index 0000000000..f2a0a8ec78 --- /dev/null +++ b/e2e/tests/functional/plugins/styling/conditional/displayLayoutConditionalStyling.e2e.spec.js @@ -0,0 +1,114 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2024, 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. + *****************************************************************************/ + +import { fileURLToPath } from 'url'; + +import { + createDomainObjectWithDefaults, + navigateToObjectWithRealTime +} from '../../../../../appActions.js'; +import { expect, test } from '../../../../../pluginFixtures.js'; + +const TINY_IMAGE_BASE64 = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII'; + +test.describe('Display Layout Conditional Styling', () => { + test.use({ + storageState: fileURLToPath( + new URL('../../../../../test-data/condition_set_storage.json', import.meta.url) + ) + }); + + let displayLayout; + test.beforeEach(async ({ page }) => { + await page.goto('./', { waitUntil: 'domcontentloaded' }); + displayLayout = await createDomainObjectWithDefaults(page, { + type: 'Display Layout', + name: 'Test Display Layout' + }); + }); + + test('Image Drawing Object can have visibility toggled conditionally', async ({ page }) => { + await page.getByLabel('Edit Object').click(); + + // Add Image Drawing Object to the layout + await page.getByLabel('Add Drawing Object').click(); + await page.getByLabel('Image').click(); + await page.getByLabel('Image URL').fill(TINY_IMAGE_BASE64); + await page.getByText('Ok').click(); + + // Use the "Test Condition Set" for conditional styling on the image + await page.getByRole('tab', { name: 'Styles' }).click(); + await page.getByRole('button', { name: 'Use Conditional Styling...' }).click(); + await page.getByLabel('Modal Overlay').getByLabel('Expand My Items folder').click(); + await page.getByLabel('Modal Overlay').getByLabel('Preview Test Condition Set').click(); + await page.getByText('Ok').click(); + + // Set the image to be hidden when the condition is met + await page.getByTitle('Visible').first().click(); + await page.getByLabel('Save Style').first().click(); + await page.getByLabel('Save', { exact: true }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // Switch to real-time mode and verify that the image toggles visibility + await navigateToObjectWithRealTime(page, displayLayout.url); + await expect(page.getByLabel('Image View')).toBeVisible(); + await expect(page.getByLabel('Image View')).toBeHidden(); + + // Reload the page and verify that the image toggles visibility + await page.reload({ waitUntil: 'domcontentloaded' }); + await expect(page.getByLabel('Image View')).toBeVisible(); + await expect(page.getByLabel('Image View')).toBeHidden(); + }); + + test('Alphanumeric object can have visibility toggled conditionally', async ({ page }) => { + await page.getByLabel('Edit Object').click(); + + // Add Alphanumeric Object to the layout + await page.getByLabel('Expand My Items folder').click(); + await page.getByLabel('Expand Test Condition Set').click(); + await page.getByLabel('Preview VIPER Rover Heading').dragTo(page.getByLabel('Layout Grid')); + + // Use the "Test Condition Set" for conditional styling on the alphanumeric + await page.getByRole('tab', { name: 'Styles' }).click(); + await page.getByRole('button', { name: 'Use Conditional Styling...' }).click(); + await page.getByLabel('Modal Overlay').getByLabel('Expand My Items folder').click(); + await page.getByLabel('Modal Overlay').getByLabel('Preview Test Condition Set').click(); + await page.getByText('Ok').click(); + + // Set the alphanumeric to be hidden when the condition is met + await page.getByTitle('Visible').first().click(); + await page.getByLabel('Save Style').first().click(); + await page.getByLabel('Save', { exact: true }).click(); + await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click(); + + // Switch to real-time mode and verify that the image toggles visibility + await navigateToObjectWithRealTime(page, displayLayout.url); + await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeVisible(); + await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeHidden(); + + // Reload the page and verify that the alphanumeric toggles visibility + await page.reload({ waitUntil: 'domcontentloaded' }); + await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeVisible(); + await expect(page.getByLabel('Alpha-numeric telemetry', { exact: true })).toBeHidden(); + }); +}); diff --git a/src/plugins/condition/components/ConditionCollection.vue b/src/plugins/condition/components/ConditionCollection.vue index ba19d006bb..84b5aff008 100644 --- a/src/plugins/condition/components/ConditionCollection.vue +++ b/src/plugins/condition/components/ConditionCollection.vue @@ -27,14 +27,16 @@ aria-label="Condition Set Condition Collection" >
- + :aria-expanded="expanded" + aria-controls="conditionContent" + @click="toggleExpanded" + >
Conditions
-
+
- Add Condition + Add Condition
diff --git a/src/plugins/displayLayout/components/ImageView.vue b/src/plugins/displayLayout/components/ImageView.vue index b8d75f52f0..b22b8d87aa 100644 --- a/src/plugins/displayLayout/components/ImageView.vue +++ b/src/plugins/displayLayout/components/ImageView.vue @@ -29,7 +29,7 @@ @end-move="endMove" > @@ -76,6 +76,9 @@ export default { }, emits: ['move', 'end-move'], computed: { + showImage() { + return this.isEditing || !this.itemStyle?.isStyleInvisible; + }, style() { let backgroundImage = 'url(' + this.item.url + ')'; let border = '1px solid ' + this.item.stroke; diff --git a/src/plugins/displayLayout/components/TelemetryView.vue b/src/plugins/displayLayout/components/TelemetryView.vue index e7987b9170..8b80e1c9f0 100644 --- a/src/plugins/displayLayout/components/TelemetryView.vue +++ b/src/plugins/displayLayout/components/TelemetryView.vue @@ -31,9 +31,10 @@