Add static limit values to LAD tables (#7193)

* limits shown

* use more verbose way for uniqueness

* when adding items, check for what columns we need

* make header logic much simpler

* add test coverage

* fix test and lint

* Update e2e/tests/functional/plugins/lad/lad.e2e.spec.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update e2e/tests/functional/plugins/lad/lad.e2e.spec.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* change to getByTitle

* lint and change to getByLabel

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
Scott Bell 2023-11-08 18:56:59 +01:00 committed by GitHub
parent 09b7873fbd
commit 51eb2a4f59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 197 additions and 102 deletions

View File

@ -25,21 +25,24 @@ const {
createDomainObjectWithDefaults,
setStartOffset,
setFixedTimeMode,
setRealTimeMode
setRealTimeMode,
openObjectTreeContextMenu
} = require('../../../../appActions');
test.describe('Testing LAD table configuration', () => {
let ladTable;
let sineWaveObject;
test.beforeEach(async ({ page }) => {
await page.goto('./', { waitUntil: 'domcontentloaded' });
// Create LAD table
const ladTable = await createDomainObjectWithDefaults(page, {
ladTable = await createDomainObjectWithDefaults(page, {
type: 'LAD Table',
name: 'Test LAD Table'
});
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
sineWaveObject = await createDomainObjectWithDefaults(page, {
type: 'Sine Wave Generator',
name: 'Test Sine Wave Generator',
parent: ladTable.uuid
@ -50,24 +53,28 @@ test.describe('Testing LAD table configuration', () => {
test('in edit mode, LAD Tables provide ability to hide columns', async ({ page }) => {
// Edit LAD table
await page.locator('[title="Edit"]').click();
// // Expand the 'My Items' folder in the left tree
// await page.locator('.c-tree__item__view-control.c-disclosure-triangle').click();
// // Add the Sine Wave Generator to the LAD table and save changes
// await page.dragAndDrop('role=treeitem[name=/Test Sine Wave Generator/]', '.c-lad-table-wrapper');
// select configuration tab in inspector
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure headers are visible initially
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// hide timestamp column
await page.getByLabel('Timestamp').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// hide units & type column
await page.getByLabel('Units').uncheck();
@ -75,6 +82,22 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// hide WATCH column
await page.getByLabel('WATCH').uncheck();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// save and reload and verify they columns are still hidden
await page.locator('button[title="Save"]').click();
@ -83,6 +106,11 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// Edit LAD table
await page.locator('[title="Edit"]').click();
@ -93,25 +121,41 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// save and reload and make sure only timestamp is still visible
// save and reload and make sure timestamp is still visible
await page.locator('button[title="Save"]').click();
await page.locator('text=Save and Finish Editing').click();
await page.reload();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Type' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// Edit LAD table
await page.locator('[title="Edit"]').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// show units and type columns
// show units, type, and WATCH columns
await page.getByLabel('Units').check();
await page.getByLabel('Type').check();
await page.getByLabel('WATCH').check();
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// save and reload and make sure all columns are still visible
await page.locator('button[title="Save"]').click();
@ -120,6 +164,56 @@ test.describe('Testing LAD table configuration', () => {
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
});
test('When adding something without Units, do not show Units column', async ({ page }) => {
// Create Sine Wave Generator
await createDomainObjectWithDefaults(page, {
type: 'Event Message Generator',
parent: ladTable.uuid
});
await page.goto(ladTable.url);
// Edit LAD table
await page.getByLabel('Edit').click();
await page.getByRole('tab', { name: 'LAD Table Configuration' }).click();
// make sure Sine Wave headers are visible initially too
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeVisible();
// save and reload and verify they columns are still hidden
await page.getByLabel('Save').click();
await page.getByRole('listitem', { name: 'Save and Finish Editing' }).click();
// Remove Sin Wave Generator
openObjectTreeContextMenu(page, sineWaveObject.url);
await page.getByRole('menuitem', { name: /Remove/ }).click();
await page.getByRole('button', { name: 'OK' }).click();
// Ensure Units & Limit columns are gone
// as Event Generator don't have them
await page.goto(ladTable.url);
await expect(page.getByRole('cell', { name: 'Timestamp' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Type' })).toBeVisible();
await expect(page.getByRole('cell', { name: 'Units' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WATCH' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'WARNING' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'DISTRESS' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'CRITICAL' })).toBeHidden();
await expect(page.getByRole('cell', { name: 'SEVERE' })).toBeHidden();
});
test("LAD Tables don't allow selection of rows but does show context click menus", async ({

View File

@ -40,6 +40,9 @@
{{ unit }}
</td>
<td v-if="showType" class="js-type-data">{{ typeLabel }}</td>
<td v-for="limit in formattedLimitValues" :key="limit.key" class="js-limit-data">
{{ limit.value }}
</td>
</tr>
</template>
@ -77,6 +80,19 @@ export default {
configuration: {
type: Object,
required: true
},
limitDefinition: {
type: Object,
default() {
return {};
}
},
limitColumnNames: {
// for ordering
type: Array,
default() {
return [];
}
}
},
emits: ['row-context-click'],
@ -85,6 +101,7 @@ export default {
datum: undefined,
timestamp: undefined,
timestampKey: undefined,
valueKey: null,
composition: [],
unit: ''
};
@ -97,6 +114,26 @@ export default {
return this.formats[this.valueKey].format(this.datum);
},
formattedLimitValues() {
if (!this.valueKey) {
return [];
}
return this.limitColumnNames.map((column) => {
if (this.limitDefinition?.[column.key]) {
const highValue = this.limitDefinition[column.key].high[this.valueKey];
const lowValue = this.limitDefinition[column.key].low[this.valueKey];
return {
key: column.key,
value: `${lowValue}${highValue}`
};
} else {
return {
key: column.key,
value: BLANK_VALUE
};
}
});
},
typeLabel() {
if (this.isAggregate) {
return 'Aggregate';

View File

@ -30,6 +30,9 @@
<th>Value</th>
<th v-if="hasUnits">Units</th>
<th v-if="showType">Type</th>
<th v-for="limitColumn in limitColumnNames" :key="limitColumn.key">
{{ limitColumn.label }}
</th>
</tr>
</thead>
<tbody>
@ -37,6 +40,8 @@
v-for="ladRow in items"
:key="ladRow.key"
:domain-object="ladRow.domainObject"
:limit-definition="ladRow.limitDefinition"
:limit-column-names="limitColumnNames"
:path-to-table="objectPath"
:has-units="hasUnits"
:is-stale="staleObjects.includes(ladRow.key)"
@ -89,6 +94,23 @@ export default {
return itemsWithUnits.length !== 0 && !this.configuration?.hiddenColumns?.units;
},
limitColumnNames() {
const limitDefinitions = [];
this.items.forEach((item) => {
if (item.limitDefinition) {
const limits = Object.keys(item.limitDefinition);
limits.forEach((limit) => {
const limitAlreadyAdded = limitDefinitions.some((limitDef) => limitDef.key === limit);
const limitHidden = this.configuration?.hiddenColumns?.[limit];
if (!limitAlreadyAdded && !limitHidden) {
limitDefinitions.push({ label: `Limit ${limit}`, key: limit });
}
});
}
});
return limitDefinitions;
},
showTimestamp() {
return !this.configuration?.hiddenColumns?.timestamp;
},
@ -149,10 +171,11 @@ export default {
this.composition.off('reorder', this.reorder);
},
methods: {
addItem(domainObject) {
async addItem(domainObject) {
let item = {};
item.domainObject = domainObject;
item.key = this.openmct.objects.makeKeyString(domainObject.identifier);
item.limitDefinition = await this.openmct.telemetry.limitDefinition(domainObject).limits();
this.items.push(item);
this.subscribeToStaleness(domainObject);

View File

@ -58,23 +58,37 @@ export default {
const ladTableConfiguration = new LADTableConfiguration(domainObject, this.openmct);
return {
headers: {
timestamp: 'Timestamp',
units: 'Units',
type: 'Type'
},
ladTableConfiguration,
isEditing: this.openmct.editor.isEditing(),
configuration: ladTableConfiguration.getConfiguration(),
items: [],
ladTableObjects: [],
ladTelemetryObjects: {}
ladTableObjects: []
};
},
computed: {
headers() {
const fullHeaders = {
timestamp: 'Timestamp',
type: 'Type'
};
// check hasUnits and limitColumnName and add then to fullHeaders
this.items.forEach((item) => {
if (item.hasUnits) {
fullHeaders.units = 'Units';
}
if (item.limitDefinition) {
const limits = Object.keys(item.limitDefinition);
limits.forEach((limit) => {
fullHeaders[limit] = limit;
});
}
});
return fullHeaders;
}
},
mounted() {
this.openmct.editor.on('isEditing', this.toggleEdit);
this.composition = this.openmct.composition.get(this.ladTableConfiguration.domainObject);
this.shouldShowUnitsCheckbox();
if (this.ladTableConfiguration.domainObject.type === 'LadTable') {
this.composition.on('add', this.addItem);
@ -104,22 +118,25 @@ export default {
}
},
methods: {
addItem(domainObject) {
async addItem(domainObject) {
const item = {};
item.domainObject = domainObject;
item.key = this.openmct.objects.makeKeyString(domainObject.identifier);
item.limitDefinition = await this.openmct.telemetry.limitDefinition(domainObject).limits();
const metadata = this.openmct.telemetry.getMetadata(domainObject);
const valueMetadatas = metadata ? metadata.valueMetadatas : [];
const metadataWithUnits = valueMetadatas.filter((metadatum) => metadatum.unit);
item.hasUnits = metadataWithUnits.length > 0;
this.items.push(item);
this.shouldShowUnitsCheckbox();
},
removeItem(identifier) {
const keystring = this.openmct.objects.makeKeyString(identifier);
const index = this.items.findIndex((item) => keystring === item.key);
this.items.splice(index, 1);
this.shouldShowUnitsCheckbox();
},
addLadTable(domainObject) {
let ladTable = {};
@ -130,20 +147,12 @@ export default {
this.ladTableObjects.push(ladTable);
const composition = this.openmct.composition.get(ladTable.domainObject);
const addCallback = this.addTelemetryObject(ladTable);
const removeCallback = this.removeTelemetryObject(ladTable);
composition.on('add', addCallback);
composition.on('remove', removeCallback);
composition.load();
this.compositions.push({
composition,
addCallback,
removeCallback
composition
});
this.shouldShowUnitsCheckbox();
},
removeLadTable(identifier) {
const index = this.ladTableObjects.findIndex(
@ -153,36 +162,6 @@ export default {
delete this.ladTelemetryObjects[ladTable.key];
this.ladTableObjects.splice(index, 1);
this.shouldShowUnitsCheckbox();
},
addTelemetryObject(ladTable) {
return (domainObject) => {
const telemetryObject = {};
telemetryObject.key = this.openmct.objects.makeKeyString(domainObject.identifier);
telemetryObject.domainObject = domainObject;
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
telemetryObjects.push(telemetryObject);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
this.shouldShowUnitsCheckbox();
};
},
removeTelemetryObject(ladTable) {
return (identifier) => {
const keystring = this.openmct.objects.makeKeyString(identifier);
const telemetryObjects = this.ladTelemetryObjects[ladTable.key];
const index = telemetryObjects.findIndex(
(telemetryObject) => keystring === telemetryObject.key
);
telemetryObjects.splice(index, 1);
this.ladTelemetryObjects[ladTable.key] = telemetryObjects;
this.shouldShowUnitsCheckbox();
};
},
combineKeys(ladKey, telemetryObjectKey) {
return `${ladKey}-${telemetryObjectKey}`;
@ -195,44 +174,6 @@ export default {
},
toggleEdit(isEditing) {
this.isEditing = isEditing;
},
shouldShowUnitsCheckbox() {
let showUnitsCheckbox = false;
if (this.ladTableConfiguration?.domainObject) {
if (this.ladTableConfiguration.domainObject.type === 'LadTable') {
const itemsWithUnits = this.items.filter((item) => {
return this.metadataHasUnits(item.domainObject);
});
showUnitsCheckbox = itemsWithUnits.length !== 0;
} else {
const ladTables = Object.values(this.ladTelemetryObjects);
for (const ladTable of ladTables) {
for (const telemetryObject of ladTable) {
if (this.metadataHasUnits(telemetryObject.domainObject)) {
showUnitsCheckbox = true;
}
}
}
}
}
if (showUnitsCheckbox && this.headers.units === undefined) {
this.headers.units = 'Units';
}
if (!showUnitsCheckbox && this.headers?.units) {
delete this.headers.units;
}
},
metadataHasUnits(domainObject) {
const metadata = this.openmct.telemetry.getMetadata(domainObject);
const valueMetadatas = metadata ? metadata.valueMetadatas : [];
const metadataWithUnits = valueMetadatas.filter((metadatum) => metadatum.unit);
return metadataWithUnits.length > 0;
}
}
};