Merge branch 'master' into telemetry-comps

This commit is contained in:
Scott Bell 2024-09-10 10:28:36 +02:00 committed by GitHub
commit e02217ad63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 310 additions and 91 deletions

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
@ -151,7 +152,10 @@ export default {
};
},
computed: {
itemClasses() {
showTelemetry() {
return this.isEditing || !this.itemStyle?.isStyleInvisible;
},
classNames() {
let classes = [];
if (this.status) {

View File

@ -25,6 +25,7 @@ import _ from 'lodash';
import StalenessUtils from '../../utils/staleness.js';
import TableRowCollection from './collections/TableRowCollection.js';
import { MODE, ORDER } from './constants.js';
import TelemetryTableColumn from './TelemetryTableColumn.js';
import TelemetryTableConfiguration from './TelemetryTableConfiguration.js';
import TelemetryTableNameColumn from './TelemetryTableNameColumn.js';
@ -119,7 +120,7 @@ export default class TelemetryTable extends EventEmitter {
this.rowLimit = rowLimit;
}
if (this.telemetryMode === 'performance') {
if (this.telemetryMode === MODE.PERFORMANCE) {
this.tableRows.setLimit(this.rowLimit);
} else {
this.tableRows.removeLimit();
@ -135,7 +136,7 @@ export default class TelemetryTable extends EventEmitter {
//If no persisted sort order, default to sorting by time system, descending.
sortOptions = sortOptions || {
key: this.openmct.time.getTimeSystem().key,
direction: 'desc'
direction: ORDER.DESCENDING
};
this.updateRowLimit();
@ -172,10 +173,9 @@ export default class TelemetryTable extends EventEmitter {
this.removeTelemetryCollection(keyString);
let sortOptions = this.configuration.getConfiguration().sortOptions;
requestOptions.order =
sortOptions?.direction ?? (this.telemetryMode === 'performance' ? 'desc' : 'asc');
requestOptions.order = sortOptions?.direction ?? ORDER.DESCENDING; // default to descending
if (this.telemetryMode === 'performance') {
if (this.telemetryMode === MODE.PERFORMANCE) {
requestOptions.size = this.rowLimit;
requestOptions.enforceSize = true;
}

View File

@ -20,6 +20,8 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { MODE } from './constants.js';
export default function getTelemetryTableType(options) {
let { telemetryMode, persistModeChange, rowLimit } = options;
@ -36,11 +38,11 @@ export default function getTelemetryTableType(options) {
control: 'select',
options: [
{
value: 'performance',
value: MODE.PERFORMANCE,
name: 'Limited (Performance) Mode'
},
{
value: 'unlimited',
value: MODE.UNLIMITED,
name: 'Unlimited Mode'
}
],

View File

@ -22,6 +22,7 @@
import { EventEmitter } from 'eventemitter3';
import _ from 'lodash';
import { ORDER } from '../constants.js';
/**
* @constructor
*/
@ -208,7 +209,7 @@ export default class TableRowCollection extends EventEmitter {
const val1 = this.getValueForSortColumn(row1);
const val2 = this.getValueForSortColumn(row2);
if (this.sortOptions.direction === 'asc') {
if (this.sortOptions.direction === ORDER.ASCENDING) {
return val1 <= val2 ? row1 : row2;
} else {
return val1 >= val2 ? row1 : row2;
@ -373,7 +374,7 @@ export default class TableRowCollection extends EventEmitter {
getRows() {
if (this.rowLimit && this.rows.length > this.rowLimit) {
if (this.sortOptions.direction === 'desc') {
if (this.sortOptions.direction === ORDER.DESCENDING) {
return this.rows.slice(0, this.rowLimit);
} else {
return this.rows.slice(-this.rowLimit);

View File

@ -296,6 +296,7 @@ import ProgressBar from '../../../ui/components/ProgressBar.vue';
import Search from '../../../ui/components/SearchComponent.vue';
import ToggleSwitch from '../../../ui/components/ToggleSwitch.vue';
import { useResizeObserver } from '../../../ui/composables/resize.js';
import { MODE, ORDER } from '../constants.js';
import SizingRow from './SizingRow.vue';
import TableColumnHeader from './TableColumnHeader.vue';
import TableFooterIndicator from './TableFooterIndicator.vue';
@ -713,7 +714,7 @@ export default {
sortBy(columnKey) {
let timeSystemKey = this.openmct.time.getTimeSystem().key;
if (this.telemetryMode === 'performance' && columnKey !== timeSystemKey) {
if (this.telemetryMode === MODE.PERFORMANCE && columnKey !== timeSystemKey) {
this.confirmUnlimitedMode('Switch to Unlimited Telemetry and Sort', () => {
this.initiateSort(columnKey);
});
@ -724,15 +725,15 @@ export default {
initiateSort(columnKey) {
// If sorting by the same column, flip the sort direction.
if (this.sortOptions.key === columnKey) {
if (this.sortOptions.direction === 'asc') {
this.sortOptions.direction = 'desc';
if (this.sortOptions.direction === ORDER.ASCENDING) {
this.sortOptions.direction = ORDER.DESCENDING;
} else {
this.sortOptions.direction = 'asc';
this.sortOptions.direction = ORDER.ASCENDING;
}
} else {
this.sortOptions = {
key: columnKey,
direction: 'desc'
direction: ORDER.DESCENDING
};
}
@ -751,7 +752,7 @@ export default {
}
},
shouldAutoScroll() {
if (this.sortOptions.direction === 'desc') {
if (this.sortOptions.direction === ORDER.DESCENDING) {
return false;
}
@ -844,7 +845,7 @@ export default {
return justTheData;
},
exportAllDataAsCSV() {
if (this.telemetryMode === 'performance') {
if (this.telemetryMode === MODE.PERFORMANCE) {
this.confirmUnlimitedMode('Switch to Unlimited Telemetry and Export', () => {
const data = this.getTableRowData();
@ -1226,7 +1227,8 @@ export default {
});
},
updateTelemetryMode() {
this.telemetryMode = this.telemetryMode === 'unlimited' ? 'performance' : 'unlimited';
this.telemetryMode =
this.telemetryMode === MODE.UNLIMITED ? MODE.PERFORMANCE : MODE.UNLIMITED;
if (this.persistModeChange) {
this.table.configuration.setTelemetryMode(this.telemetryMode);
@ -1236,7 +1238,7 @@ export default {
const timeSystemKey = this.openmct.time.getTimeSystem().key;
if (this.telemetryMode === 'performance' && this.sortOptions.key !== timeSystemKey) {
if (this.telemetryMode === MODE.PERFORMANCE && this.sortOptions.key !== timeSystemKey) {
this.openmct.notifications.info(
'Switched to Performance Mode: Table now sorted by time for optimized efficiency.'
);

View File

@ -62,6 +62,8 @@
<script>
import _ from 'lodash';
import { MODE } from '../constants.js';
const FILTER_INDICATOR_LABEL = 'Filters:';
const FILTER_INDICATOR_LABEL_MIXED = 'Mixed Filters:';
const FILTER_INDICATOR_TITLE = 'Data filters are being applied to this view.';
@ -81,7 +83,7 @@ export default {
},
telemetryMode: {
type: String,
default: 'performance'
default: MODE.PERFORMANCE
}
},
emits: ['telemetry-mode-change'],
@ -103,7 +105,7 @@ export default {
});
},
isUnlimitedMode() {
return this.telemetryMode === 'unlimited';
return this.telemetryMode === MODE.UNLIMITED;
},
label() {
if (this.hasMixedFilters) {

View File

@ -0,0 +1,11 @@
const ORDER = {
ASCENDING: 'asc',
DESCENDING: 'desc'
};
const MODE = {
PERFORMANCE: 'performance',
UNLIMITED: 'unlimited'
};
export { MODE, ORDER };

View File

@ -20,13 +20,14 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { MODE } from './constants.js';
import TableConfigurationViewProvider from './TableConfigurationViewProvider.js';
import getTelemetryTableType from './TelemetryTableType.js';
import TelemetryTableViewProvider from './TelemetryTableViewProvider.js';
import TelemetryTableViewActions from './ViewActions.js';
export default function plugin(
options = { telemetryMode: 'performance', persistModeChange: true, rowLimit: 50 }
options = { telemetryMode: MODE.PERFORMANCE, persistModeChange: true, rowLimit: 50 }
) {
return function install(openmct) {
openmct.objectViews.addProvider(new TelemetryTableViewProvider(openmct, options));

View File

@ -28,6 +28,7 @@ import {
} from 'utils/testing';
import { nextTick } from 'vue';
import { MODE } from './constants.js';
import TablePlugin from './plugin.js';
class MockDataTransfer {
@ -198,7 +199,7 @@ describe('the plugin', () => {
},
persistModeChange: true,
rowLimit: 50,
telemetryMode: 'performance'
telemetryMode: MODE.PERFORMANCE
}
};
const testTelemetry = [

View File

@ -25,18 +25,19 @@
*
* @interface ToolbarRegistry
*/
export default function ToolbarRegistry() {
export default class ToolbarRegistry {
constructor() {
this.providers = {};
}
}
/**
/**
* Gets toolbar controls from providers which can provide a toolbar for this selection.
*
* @param {Object} selection the selection object
* @returns {Object[]} an array of objects defining controls for the toolbar
* @private for platform-internal use
*/
ToolbarRegistry.prototype.get = function (selection) {
get(selection) {
const providers = this.getAllProviders().filter(function (provider) {
return provider.forSelection(selection);
});
@ -48,29 +49,29 @@ ToolbarRegistry.prototype.get = function (selection) {
});
return structure;
};
}
/**
/**
* @private
*/
ToolbarRegistry.prototype.getAllProviders = function () {
getAllProviders() {
return Object.values(this.providers);
};
}
/**
/**
* @private
*/
ToolbarRegistry.prototype.getByProviderKey = function (key) {
getByProviderKey(key) {
return this.providers[key];
};
}
/**
/**
* Registers a new type of toolbar.
*
* @param {module:openmct.ToolbarRegistry} provider the provider for this toolbar
* @method addProvider
*/
ToolbarRegistry.prototype.addProvider = function (provider) {
addProvider(provider) {
const key = provider.key;
if (key === undefined) {
@ -82,7 +83,8 @@ ToolbarRegistry.prototype.addProvider = function (provider) {
}
this.providers[key] = provider;
};
}
}
/**
* Exposes types of toolbars in Open MCT.

View File

@ -34,9 +34,9 @@
}"
@click="onClick"
>
<div v-if="options.label" class="c-icon-button__label">
<span v-if="options.label" class="c-icon-button__label">
{{ options.label }}
</div>
</span>
</div>
</div>
</template>

View File

@ -25,11 +25,13 @@
class="c-icon-button c-icon-button--menu"
:class="options.icon"
:title="options.title"
:aria-label="options.label"
role="button"
@click="toggle"
>
<div v-if="options.label" class="c-icon-button__label">
<span v-if="options.label" class="c-icon-button__label">
{{ options.label }}
</div>
</span>
</div>
<div v-if="open" class="c-menu" role="menu">
<ul>