Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(conditional styling): conditional visibility for images and alpha-numerics in display layouts #7824

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions e2e/test-data/condition_set_storage.json
Original file line number Diff line number Diff line change
@@ -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}}]"
}
]
}
]
}
49 changes: 49 additions & 0 deletions e2e/tests/framework/generateLocalStorageData.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
});
});
13 changes: 8 additions & 5 deletions src/plugins/condition/components/ConditionCollection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@
aria-label="Condition Set Condition Collection"
>
<div class="c-cs__header c-section__header">
<span
<button
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
:class="{ 'c-disclosure-triangle--expanded': expanded }"
@click="expanded = !expanded"
></span>
:aria-expanded="expanded"
aria-controls="conditionContent"
@click="toggleExpanded"
></button>
<div class="c-cs__header-label c-section__label">Conditions</div>
</div>
<div v-if="expanded" class="c-cs__content">
<div v-if="expanded" id="conditionContent" class="c-cs__content">
<div
v-show="isEditing"
class="hint"
Expand All @@ -54,9 +56,10 @@
v-show="isEditing"
id="addCondition"
class="c-button c-button--major icon-plus labeled"
aria-labelledby="addConditionButtonLabel"
@click="addCondition"
>
<span class="c-cs-button__label">Add Condition</span>
<span id="addConditionButtonLabel" class="c-cs-button__label">Add Condition</span>
</button>

<div class="c-cs__conditions-h" :class="{ 'is-active-dragging': isDragging }">
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/displayLayout/components/ImageView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
@end-move="endMove"
>
<template #content>
<div class="c-image-view" :style="style"></div>
<div v-show="showImage" aria-label="Image View" class="c-image-view" :style="style"></div>
</template>
</LayoutFrame>
</template>
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/displayLayout/components/TelemetryView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
<template #content>
<div
v-if="domainObject"
v-show="showTelemetry"
ref="telemetryViewWrapper"
class="c-telemetry-view u-style-receiver"
:class="[itemClasses]"
:class="classNames"
:style="styleObject"
:data-font-size="item.fontSize"
:data-font="item.font"
Expand Down Expand Up @@ -151,7 +152,10 @@ export default {
};
},
computed: {
itemClasses() {
showTelemetry() {
return this.isEditing || !this.itemStyle?.isStyleInvisible;
},
classNames() {
let classes = [];

if (this.status) {
Expand Down
Loading
Loading