fix(#7215)[Fault Management]: Fix shelving and acknowledging faults with single and bulk actions (#7559)

* refactor: merge FaultManagementListView into FaultManagementView

* refactor: make `selectedFaults` a computed property

* refactor: use named exports

* fix: reset fault map AFTER selectedFaults have been acknowledged

* a11y: add aria labels for fault management toolbar buttons

* refactor: use named import/exports

* a11y: add label

* a11y: add aria label for checkboxes

* fix: acknowledging or shelving single fault from context menu should only apply to selected fault

* refactor: use change event instead of input event for checkbox

* test: fix e2e tests, remove expect.softs

* test: stabilize fault management e2e tests
This commit is contained in:
Jesse Mazzella 2024-03-06 15:28:38 -08:00 committed by GitHub
parent df969722d1
commit ab49f3f3a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 512 additions and 514 deletions

View File

@ -21,10 +21,12 @@
*****************************************************************************/
import { fileURLToPath } from 'url';
import { expect } from '../pluginFixtures.js';
/**
* @param {import('@playwright/test').Page} page
*/
async function navigateToFaultManagementWithExample(page) {
export async function navigateToFaultManagementWithExample(page) {
await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProvider.js', import.meta.url))
});
@ -35,7 +37,7 @@ async function navigateToFaultManagementWithExample(page) {
/**
* @param {import('@playwright/test').Page} page
*/
async function navigateToFaultManagementWithStaticExample(page) {
export async function navigateToFaultManagementWithStaticExample(page) {
await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProviderStatic.js', import.meta.url))
});
@ -46,7 +48,7 @@ async function navigateToFaultManagementWithStaticExample(page) {
/**
* @param {import('@playwright/test').Page} page
*/
async function navigateToFaultManagementWithoutExample(page) {
export async function navigateToFaultManagementWithoutExample(page) {
await page.addInitScript({
path: fileURLToPath(new URL('./addInitFaultManagementPlugin.js', import.meta.url))
});
@ -57,7 +59,7 @@ async function navigateToFaultManagementWithoutExample(page) {
/**
* @param {import('@playwright/test').Page} page
*/
async function navigateToFaultItemInTree(page) {
export async function navigateToFaultItemInTree(page) {
await page.goto('./', { waitUntil: 'networkidle' });
const faultManagementTreeItem = page
@ -75,88 +77,95 @@ async function navigateToFaultItemInTree(page) {
/**
* @param {import('@playwright/test').Page} page
*/
async function acknowledgeFault(page, rowNumber) {
export async function acknowledgeFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Acknowledge"').click();
// Click [aria-label="Save"]
await page.locator('[aria-label="Save"]').click();
await page.getByLabel('Acknowledge', { exact: true }).click();
await page.getByLabel('Save').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function shelveMultipleFaults(page, ...nums) {
export async function shelveMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => {
return selectFaultItem(page, num);
});
await Promise.all(selectRows);
await page.locator('button:has-text("Shelve")').click();
await page.locator('[aria-label="Save"]').click();
await page.getByLabel('Shelve selected faults').click();
await page.getByLabel('Save').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function acknowledgeMultipleFaults(page, ...nums) {
export async function acknowledgeMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => {
return selectFaultItem(page, num);
});
await Promise.all(selectRows);
await page.locator('button:has-text("Acknowledge")').click();
await page.locator('[aria-label="Save"]').click();
await page.getByLabel('Save').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function shelveFault(page, rowNumber) {
export async function shelveFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Shelve"').click();
// Click [aria-label="Save"]
await page.locator('[aria-label="Save"]').click();
await page.getByLabel('Save').click();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function changeViewTo(page, view) {
export async function changeViewTo(page, view) {
await page.locator('.c-fault-mgmt__search-row select').first().selectOption(view);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function sortFaultsBy(page, sort) {
export async function sortFaultsBy(page, sort) {
await page.locator('.c-fault-mgmt__list-header-sortButton select').selectOption(sort);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function enterSearchTerm(page, term) {
export async function enterSearchTerm(page, term) {
await page.locator('.c-fault-mgmt-search [aria-label="Search Input"]').fill(term);
}
/**
* @param {import('@playwright/test').Page} page
*/
async function clearSearch(page) {
export async function clearSearch(page) {
await enterSearchTerm(page, '');
}
/**
* @param {import('@playwright/test').Page} page
*/
async function selectFaultItem(page, rowNumber) {
await page.locator(`.c-fault-mgmt-item > input >> nth=${rowNumber - 1}`).check();
export async function selectFaultItem(page, rowNumber) {
await page
.getByLabel('Select fault')
.nth(rowNumber - 1)
.check({
// Need force here because checkbox state is changed by an event emitted by the checkbox
// eslint-disable-next-line playwright/no-force-option
force: true
});
await expect(page.getByLabel('Select fault').nth(rowNumber - 1)).toBeChecked();
}
/**
* @param {import('@playwright/test').Page} page
*/
async function getHighestSeverity(page) {
export async function getHighestSeverity(page) {
const criticalCount = await page.locator('[title=CRITICAL]').count();
const warningCount = await page.locator('[title=WARNING]').count();
@ -172,7 +181,7 @@ async function getHighestSeverity(page) {
/**
* @param {import('@playwright/test').Page} page
*/
async function getLowestSeverity(page) {
export async function getLowestSeverity(page) {
const warningCount = await page.locator('[title=WARNING]').count();
const watchCount = await page.locator('[title=WATCH]').count();
@ -188,7 +197,7 @@ async function getLowestSeverity(page) {
/**
* @param {import('@playwright/test').Page} page
*/
async function getFaultResultCount(page) {
export async function getFaultResultCount(page) {
const count = await page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').count();
return count;
@ -197,7 +206,7 @@ async function getFaultResultCount(page) {
/**
* @param {import('@playwright/test').Page} page
*/
function getFault(page, rowNumber) {
export function getFault(page, rowNumber) {
const fault = page.locator(
`.c-faults-list-view-item-body > .c-fault-mgmt__list >> nth=${rowNumber - 1}`
);
@ -208,7 +217,7 @@ function getFault(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
function getFaultByName(page, name) {
export function getFaultByName(page, name) {
const fault = page.locator(`.c-fault-mgmt__list-faultname:has-text("${name}")`);
return fault;
@ -217,7 +226,7 @@ function getFaultByName(page, name) {
/**
* @param {import('@playwright/test').Page} page
*/
async function getFaultName(page, rowNumber) {
export async function getFaultName(page, rowNumber) {
const faultName = await page
.locator(`.c-fault-mgmt__list-faultname >> nth=${rowNumber - 1}`)
.textContent();
@ -228,7 +237,7 @@ async function getFaultName(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
async function getFaultSeverity(page, rowNumber) {
export async function getFaultSeverity(page, rowNumber) {
const faultSeverity = await page
.locator(`.c-faults-list-view-item-body .c-fault-mgmt__list-severity >> nth=${rowNumber - 1}`)
.getAttribute('title');
@ -239,7 +248,7 @@ async function getFaultSeverity(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
async function getFaultNamespace(page, rowNumber) {
export async function getFaultNamespace(page, rowNumber) {
const faultNamespace = await page
.locator(`.c-fault-mgmt__list-path >> nth=${rowNumber - 1}`)
.textContent();
@ -250,7 +259,7 @@ async function getFaultNamespace(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
async function getFaultTriggerTime(page, rowNumber) {
export async function getFaultTriggerTime(page, rowNumber) {
const faultTriggerTime = await page
.locator(`.c-fault-mgmt__list-trigTime >> nth=${rowNumber - 1} >> .c-fault-mgmt-item__value`)
.textContent();
@ -261,35 +270,10 @@ async function getFaultTriggerTime(page, rowNumber) {
/**
* @param {import('@playwright/test').Page} page
*/
async function openFaultRowMenu(page, rowNumber) {
export async function openFaultRowMenu(page, rowNumber) {
// select
await page
.locator(`.c-fault-mgmt-item > .c-fault-mgmt__list-action-button >> nth=${rowNumber - 1}`)
.getByLabel('Disposition actions')
.nth(rowNumber - 1)
.click();
}
export {
acknowledgeFault,
acknowledgeMultipleFaults,
changeViewTo,
clearSearch,
enterSearchTerm,
getFault,
getFaultByName,
getFaultName,
getFaultNamespace,
getFaultResultCount,
getFaultSeverity,
getFaultTriggerTime,
getHighestSeverity,
getLowestSeverity,
navigateToFaultItemInTree,
navigateToFaultManagementWithExample,
navigateToFaultManagementWithoutExample,
navigateToFaultManagementWithStaticExample,
openFaultRowMenu,
selectFaultItem,
shelveFault,
shelveMultipleFaults,
sortFaultsBy
};

View File

@ -20,25 +20,46 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import * as utils from '../../../../helper/faultUtils.js';
import {
acknowledgeFault,
acknowledgeMultipleFaults,
changeViewTo,
clearSearch,
enterSearchTerm,
getFault,
getFaultByName,
getFaultName,
getFaultNamespace,
getFaultResultCount,
getFaultSeverity,
getFaultTriggerTime,
getHighestSeverity,
getLowestSeverity,
navigateToFaultManagementWithExample,
navigateToFaultManagementWithoutExample,
selectFaultItem,
shelveFault,
shelveMultipleFaults,
sortFaultsBy
} from '../../../../helper/faultUtils.js';
import { expect, test } from '../../../../pluginFixtures.js';
test.describe('The Fault Management Plugin using example faults', () => {
test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithExample(page);
await navigateToFaultManagementWithExample(page);
});
test('Shows a criticality icon for every fault @unstable', async ({ page }) => {
test('Shows a criticality icon for every fault', async ({ page }) => {
const faultCount = await page.locator('c-fault-mgmt__list').count();
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').count();
expect.soft(faultCount).toEqual(criticalityIconCount);
expect(faultCount).toEqual(criticalityIconCount);
});
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector @unstable', async ({
test('When selecting a fault, it has an "is-selected" class and it\'s information shows in the inspector', async ({
page
}) => {
await utils.selectFaultItem(page, 1);
await selectFaultItem(page, 1);
await page.getByRole('tab', { name: 'Config' }).click();
const selectedFaultName = await page
@ -48,22 +69,22 @@ test.describe('The Fault Management Plugin using example faults', () => {
.locator(`.c-inspector__properties >> :text("${selectedFaultName}")`)
.count();
await expect
.soft(page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first())
.toHaveClass(/is-selected/);
expect.soft(inspectorFaultNameCount).toEqual(1);
await expect(
page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first()
).toHaveClass(/is-selected/);
expect(inspectorFaultNameCount).toEqual(1);
});
test('When selecting multiple faults, no specific fault information is shown in the inspector @unstable', async ({
test('When selecting multiple faults, no specific fault information is shown in the inspector', async ({
page
}) => {
await utils.selectFaultItem(page, 1);
await utils.selectFaultItem(page, 2);
await selectFaultItem(page, 1);
await selectFaultItem(page, 2);
const selectedRows = page.locator(
'.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname'
);
expect.soft(await selectedRows.count()).toEqual(2);
expect(await selectedRows.count()).toEqual(2);
await page.getByRole('tab', { name: 'Config' }).click();
const firstSelectedFaultName = await selectedRows.nth(0).textContent();
@ -75,180 +96,180 @@ test.describe('The Fault Management Plugin using example faults', () => {
.locator(`.c-inspector__properties >> :text("${secondSelectedFaultName}")`)
.count();
expect.soft(firstNameInInspectorCount).toEqual(0);
expect.soft(secondNameInInspectorCount).toEqual(0);
expect(firstNameInInspectorCount).toEqual(0);
expect(secondNameInInspectorCount).toEqual(0);
});
test('Allows you to shelve a fault @unstable', async ({ page }) => {
const shelvedFaultName = await utils.getFaultName(page, 2);
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName);
test('Allows you to shelve a fault', async ({ page }) => {
const shelvedFaultName = await getFaultName(page, 2);
const beforeShelvedFault = getFaultByName(page, shelvedFaultName);
expect.soft(await beforeShelvedFault.count()).toBe(1);
await expect(beforeShelvedFault).toHaveCount(1);
await utils.shelveFault(page, 2);
await shelveFault(page, 2);
// check it is removed from standard view
const afterShelvedFault = utils.getFaultByName(page, shelvedFaultName);
expect.soft(await afterShelvedFault.count()).toBe(0);
const afterShelvedFault = getFaultByName(page, shelvedFaultName);
expect(await afterShelvedFault.count()).toBe(0);
await utils.changeViewTo(page, 'shelved');
await changeViewTo(page, 'shelved');
const shelvedViewFault = utils.getFaultByName(page, shelvedFaultName);
const shelvedViewFault = getFaultByName(page, shelvedFaultName);
expect.soft(await shelvedViewFault.count()).toBe(1);
expect(await shelvedViewFault.count()).toBe(1);
});
test('Allows you to acknowledge a fault @unstable', async ({ page }) => {
const acknowledgedFaultName = await utils.getFaultName(page, 3);
test('Allows you to acknowledge a fault', async ({ page }) => {
const acknowledgedFaultName = await getFaultName(page, 3);
await utils.acknowledgeFault(page, 3);
await acknowledgeFault(page, 3);
const fault = utils.getFault(page, 3);
await expect.soft(fault).toHaveClass(/is-acknowledged/);
const fault = getFault(page, 3);
await expect(fault).toHaveClass(/is-acknowledged/);
await utils.changeViewTo(page, 'acknowledged');
await changeViewTo(page, 'acknowledged');
const acknowledgedViewFaultName = await utils.getFaultName(page, 1);
expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
const acknowledgedViewFaultName = await getFaultName(page, 1);
expect(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
});
test('Allows you to shelve multiple faults @unstable', async ({ page }) => {
const shelvedFaultNameOne = await utils.getFaultName(page, 1);
const shelvedFaultNameFour = await utils.getFaultName(page, 4);
test('Allows you to shelve multiple faults', async ({ page }) => {
const shelvedFaultNameOne = await getFaultName(page, 1);
const shelvedFaultNameFour = await getFaultName(page, 4);
const beforeShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne);
const beforeShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour);
const beforeShelvedFaultOne = getFaultByName(page, shelvedFaultNameOne);
const beforeShelvedFaultFour = getFaultByName(page, shelvedFaultNameFour);
expect.soft(await beforeShelvedFaultOne.count()).toBe(1);
expect.soft(await beforeShelvedFaultFour.count()).toBe(1);
await expect(beforeShelvedFaultOne).toHaveCount(1);
await expect(beforeShelvedFaultFour).toHaveCount(1);
await utils.shelveMultipleFaults(page, 1, 4);
await shelveMultipleFaults(page, 1, 4);
// check it is removed from standard view
const afterShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne);
const afterShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour);
expect.soft(await afterShelvedFaultOne.count()).toBe(0);
expect.soft(await afterShelvedFaultFour.count()).toBe(0);
const afterShelvedFaultOne = getFaultByName(page, shelvedFaultNameOne);
const afterShelvedFaultFour = getFaultByName(page, shelvedFaultNameFour);
await expect(afterShelvedFaultOne).toHaveCount(0);
await expect(afterShelvedFaultFour).toHaveCount(0);
await utils.changeViewTo(page, 'shelved');
await changeViewTo(page, 'shelved');
const shelvedViewFaultOne = utils.getFaultByName(page, shelvedFaultNameOne);
const shelvedViewFaultFour = utils.getFaultByName(page, shelvedFaultNameFour);
const shelvedViewFaultOne = getFaultByName(page, shelvedFaultNameOne);
const shelvedViewFaultFour = getFaultByName(page, shelvedFaultNameFour);
expect.soft(await shelvedViewFaultOne.count()).toBe(1);
expect.soft(await shelvedViewFaultFour.count()).toBe(1);
await expect(shelvedViewFaultOne).toHaveCount(1);
await expect(shelvedViewFaultFour).toHaveCount(1);
});
test('Allows you to acknowledge multiple faults @unstable', async ({ page }) => {
const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2);
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5);
test('Allows you to acknowledge multiple faults', async ({ page }) => {
const acknowledgedFaultNameTwo = await getFaultName(page, 2);
const acknowledgedFaultNameFive = await getFaultName(page, 5);
await utils.acknowledgeMultipleFaults(page, 2, 5);
await acknowledgeMultipleFaults(page, 2, 5);
const faultTwo = utils.getFault(page, 2);
const faultFive = utils.getFault(page, 5);
const faultTwo = getFault(page, 2);
const faultFive = getFault(page, 5);
// check they have been acknowledged
await expect.soft(faultTwo).toHaveClass(/is-acknowledged/);
await expect.soft(faultFive).toHaveClass(/is-acknowledged/);
await expect(faultTwo).toHaveClass(/is-acknowledged/);
await expect(faultFive).toHaveClass(/is-acknowledged/);
await utils.changeViewTo(page, 'acknowledged');
await changeViewTo(page, 'acknowledged');
const acknowledgedViewFaultTwo = utils.getFaultByName(page, acknowledgedFaultNameTwo);
const acknowledgedViewFaultFive = utils.getFaultByName(page, acknowledgedFaultNameFive);
const acknowledgedViewFaultTwo = getFaultByName(page, acknowledgedFaultNameTwo);
const acknowledgedViewFaultFive = getFaultByName(page, acknowledgedFaultNameFive);
expect.soft(await acknowledgedViewFaultTwo.count()).toBe(1);
expect.soft(await acknowledgedViewFaultFive.count()).toBe(1);
await expect(acknowledgedViewFaultTwo).toHaveCount(1);
await expect(acknowledgedViewFaultFive).toHaveCount(1);
});
test('Allows you to search faults @unstable', async ({ page }) => {
const faultThreeNamespace = await utils.getFaultNamespace(page, 3);
const faultTwoName = await utils.getFaultName(page, 2);
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5);
test('Allows you to search faults', async ({ page }) => {
const faultThreeNamespace = await getFaultNamespace(page, 3);
const faultTwoName = await getFaultName(page, 2);
const faultFiveTriggerTime = await getFaultTriggerTime(page, 5);
// should be all faults (5)
let faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5);
let faultResultCount = await getFaultResultCount(page);
expect(faultResultCount).toEqual(5);
// search namespace
await utils.enterSearchTerm(page, faultThreeNamespace);
await enterSearchTerm(page, faultThreeNamespace);
faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultNamespace(page, 1)).toEqual(faultThreeNamespace);
faultResultCount = await getFaultResultCount(page);
expect(faultResultCount).toEqual(1);
expect(await getFaultNamespace(page, 1)).toEqual(faultThreeNamespace);
// all faults
await utils.clearSearch(page);
faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5);
await clearSearch(page);
faultResultCount = await getFaultResultCount(page);
expect(faultResultCount).toEqual(5);
// search name
await utils.enterSearchTerm(page, faultTwoName);
await enterSearchTerm(page, faultTwoName);
faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultName(page, 1)).toEqual(faultTwoName);
faultResultCount = await getFaultResultCount(page);
expect(faultResultCount).toEqual(1);
expect(await getFaultName(page, 1)).toEqual(faultTwoName);
// all faults
await utils.clearSearch(page);
faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5);
await clearSearch(page);
faultResultCount = await getFaultResultCount(page);
expect(faultResultCount).toEqual(5);
// search triggerTime
await utils.enterSearchTerm(page, faultFiveTriggerTime);
await enterSearchTerm(page, faultFiveTriggerTime);
faultResultCount = await utils.getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
faultResultCount = await getFaultResultCount(page);
expect(faultResultCount).toEqual(1);
expect(await getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
});
test('Allows you to sort faults @unstable', async ({ page }) => {
const highestSeverity = await utils.getHighestSeverity(page);
const lowestSeverity = await utils.getLowestSeverity(page);
test('Allows you to sort faults', async ({ page }) => {
const highestSeverity = await getHighestSeverity(page);
const lowestSeverity = await getLowestSeverity(page);
const faultOneName = 'Example Fault 1';
const faultFiveName = 'Example Fault 5';
let firstFaultName = await utils.getFaultName(page, 1);
let firstFaultName = await getFaultName(page, 1);
expect.soft(firstFaultName).toEqual(faultOneName);
expect(firstFaultName).toEqual(faultOneName);
await utils.sortFaultsBy(page, 'oldest-first');
await sortFaultsBy(page, 'oldest-first');
firstFaultName = await utils.getFaultName(page, 1);
expect.soft(firstFaultName).toEqual(faultFiveName);
firstFaultName = await getFaultName(page, 1);
expect(firstFaultName).toEqual(faultFiveName);
await utils.sortFaultsBy(page, 'severity');
await sortFaultsBy(page, 'severity');
const sortedHighestSeverity = await utils.getFaultSeverity(page, 1);
const sortedLowestSeverity = await utils.getFaultSeverity(page, 5);
expect.soft(sortedHighestSeverity).toEqual(highestSeverity);
expect.soft(sortedLowestSeverity).toEqual(lowestSeverity);
const sortedHighestSeverity = await getFaultSeverity(page, 1);
const sortedLowestSeverity = await getFaultSeverity(page, 5);
expect(sortedHighestSeverity).toEqual(highestSeverity);
expect(sortedLowestSeverity).toEqual(lowestSeverity);
});
});
test.describe('The Fault Management Plugin without using example faults', () => {
test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithoutExample(page);
await navigateToFaultManagementWithoutExample(page);
});
test('Shows no faults when no faults are provided @unstable', async ({ page }) => {
test('Shows no faults when no faults are provided', async ({ page }) => {
const faultCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(faultCount).toEqual(0);
expect(faultCount).toEqual(0);
await utils.changeViewTo(page, 'acknowledged');
await changeViewTo(page, 'acknowledged');
const acknowledgedCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(acknowledgedCount).toEqual(0);
expect(acknowledgedCount).toEqual(0);
await utils.changeViewTo(page, 'shelved');
await changeViewTo(page, 'shelved');
const shelvedCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(shelvedCount).toEqual(0);
expect(shelvedCount).toEqual(0);
});
test('Will return no faults when searching @unstable', async ({ page }) => {
await utils.enterSearchTerm(page, 'fault');
test('Will return no faults when searching', async ({ page }) => {
await enterSearchTerm(page, 'fault');
const faultCount = await page.locator('c-fault-mgmt__list').count();
expect.soft(faultCount).toEqual(0);
expect(faultCount).toEqual(0);
});
});

View File

@ -21,12 +21,20 @@
*****************************************************************************/
import percySnapshot from '@percy/playwright';
import * as utils from '../../helper/faultUtils.js';
import {
acknowledgeFault,
changeViewTo,
navigateToFaultManagementWithoutExample,
navigateToFaultManagementWithStaticExample,
openFaultRowMenu,
selectFaultItem,
shelveFault
} from '../../helper/faultUtils.js';
import { expect, test } from '../../pluginFixtures.js';
test.describe('Fault Management Visual Tests - without example', () => {
test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithoutExample(page);
await navigateToFaultManagementWithoutExample(page);
await page.getByLabel('Collapse Inspect Pane').click();
await page.getByLabel('Click to collapse items').click();
});
@ -55,7 +63,7 @@ test.describe('Fault Management Visual Tests - without example', () => {
test.describe('Fault Management Visual Tests', () => {
test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithStaticExample(page);
await navigateToFaultManagementWithStaticExample(page);
await page.getByLabel('Collapse Inspect Pane').click();
await page.getByLabel('Click to collapse items').click();
});
@ -63,8 +71,8 @@ test.describe('Fault Management Visual Tests', () => {
test('fault list and acknowledged faults', async ({ page, theme }) => {
await percySnapshot(page, `Shows a list of faults in the standard view (theme: '${theme}')`);
await utils.acknowledgeFault(page, 1);
await utils.changeViewTo(page, 'acknowledged');
await acknowledgeFault(page, 1);
await changeViewTo(page, 'acknowledged');
await percySnapshot(
page,
@ -73,12 +81,12 @@ test.describe('Fault Management Visual Tests', () => {
});
test('shelved faults', async ({ page, theme }) => {
await utils.shelveFault(page, 1);
await utils.changeViewTo(page, 'shelved');
await shelveFault(page, 1);
await changeViewTo(page, 'shelved');
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
await utils.openFaultRowMenu(page, 1);
await openFaultRowMenu(page, 1);
await percySnapshot(
page,
@ -87,7 +95,7 @@ test.describe('Fault Management Visual Tests', () => {
});
test('3-dot menu for fault', async ({ page, theme }) => {
await utils.openFaultRowMenu(page, 1);
await openFaultRowMenu(page, 1);
await percySnapshot(
page,
@ -96,7 +104,7 @@ test.describe('Fault Management Visual Tests', () => {
});
test('ability to acknowledge or shelve', async ({ page, theme }) => {
await utils.selectFaultItem(page, 1);
await selectFaultItem(page, 1);
await percySnapshot(
page,

View File

@ -20,13 +20,13 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import utils from './utils.js';
import { acknowledgeFault, randomFaults, shelveFault } from './utils.js';
export default function (staticFaults = false) {
return function install(openmct) {
openmct.install(openmct.plugins.FaultManagement());
const faultsData = utils.randomFaults(staticFaults);
const faultsData = randomFaults(staticFaults);
openmct.faults.addProvider({
request(domainObject, options) {
@ -44,14 +44,14 @@ export default function (staticFaults = false) {
return domainObject.type === 'faultManagement';
},
acknowledgeFault(fault, { comment = '' }) {
utils.acknowledgeFault(fault);
acknowledgeFault(fault);
return Promise.resolve({
success: true
});
},
shelveFault(fault, duration) {
utils.shelveFault(fault, duration);
shelveFault(fault, duration);
return Promise.resolve({
success: true

View File

@ -43,7 +43,7 @@ const getRandom = {
}
};
function shelveFault(
export function shelveFault(
fault,
opts = {
shelved: true,
@ -58,11 +58,11 @@ function shelveFault(
}, opts.shelveDuration);
}
function acknowledgeFault(fault) {
export function acknowledgeFault(fault) {
fault.acknowledged = true;
}
function randomFaults(staticFaults, count = 5) {
export function randomFaults(staticFaults, count = 5) {
let faults = [];
for (let x = 1, y = count + 1; x < y; x++) {
@ -71,9 +71,3 @@ function randomFaults(staticFaults, count = 5) {
return faults;
}
export default {
randomFaults,
shelveFault,
acknowledgeFault
};

View File

@ -23,7 +23,7 @@
<template>
<div class="c-fault-mgmt-item-header c-fault-mgmt__list-header c-fault-mgmt__list">
<div class="c-fault-mgmt-item-header c-fault-mgmt__checkbox">
<input type="checkbox" :checked="isSelectAll" @input="selectAll" />
<input type="checkbox" :checked="isSelectAll" @change="selectAll" />
</div>
<div
class="c-fault-mgmt-item-header c-fault-mgmt__list-header-results c-fault-mgmt__list-severity"

View File

@ -23,7 +23,12 @@
<template>
<div class="c-fault-mgmt__list data-selectable" :class="classesFromState">
<div class="c-fault-mgmt-item c-fault-mgmt__list-checkbox">
<input type="checkbox" :checked="isSelected" @input="toggleSelected" />
<input
type="checkbox"
:aria-label="checkBoxAriaLabel"
:checked="isSelected"
@change="toggleSelected"
/>
</div>
<div class="c-fault-mgmt-item">
<div
@ -60,6 +65,7 @@
<button
class="c-fault-mgmt__list-action-button l-browse-bar__actions c-icon-button icon-3-dots"
title="Disposition Actions"
aria-label="Disposition Actions"
@click="showActionMenu"
></button>
</div>
@ -86,13 +92,14 @@ export default {
},
isSelected: {
type: Boolean,
default: () => {
return false;
}
default: false
}
},
emits: ['acknowledge-selected', 'shelve-selected', 'toggle-selected'],
emits: ['acknowledge-selected', 'shelve-selected', 'toggle-selected', 'clear-all-selected'],
computed: {
checkBoxAriaLabel() {
return `Select fault: ${this.fault.name}`;
},
classesFromState() {
const exclusiveStates = [
{
@ -171,6 +178,7 @@ export default {
name: 'Acknowledge',
description: '',
onItemClicked: (e) => {
this.clearAllSelected();
this.$emit('acknowledge-selected', [this.fault]);
}
},
@ -179,6 +187,7 @@ export default {
name: 'Shelve',
description: '',
onItemClicked: () => {
this.clearAllSelected();
this.$emit('shelve-selected', [this.fault], { shelved: true });
}
},
@ -188,6 +197,7 @@ export default {
name: 'Unshelve',
description: '',
onItemClicked: () => {
this.clearAllSelected();
this.$emit('shelve-selected', [this.fault], { shelved: false });
}
}
@ -202,6 +212,9 @@ export default {
};
this.$emit('toggle-selected', faultData);
},
clearAllSelected() {
this.$emit('clear-all-selected');
}
}
};

View File

@ -1,307 +0,0 @@
<!--
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.
-->
<template>
<div class="c-faults-list-view">
<FaultManagementSearch
:search-term="searchTerm"
@filter-changed="updateFilter"
@update-search-term="updateSearchTerm"
/>
<FaultManagementToolbar
v-if="showToolbar"
:selected-faults="selectedFaults"
@acknowledge-selected="toggleAcknowledgeSelected"
@shelve-selected="toggleShelveSelected"
/>
<div class="c-faults-list-view-header-item-container-wrapper">
<div class="c-faults-list-view-header-item-container">
<FaultManagementListHeader
class="header"
:selected-faults="Object.values(selectedFaults)"
:total-faults-count="filteredFaultsList.length"
@select-all="selectAll"
@sort-changed="sortChanged"
/>
<div class="c-faults-list-view-item-body">
<template v-if="filteredFaultsList.length > 0">
<FaultManagementListItem
v-for="fault of filteredFaultsList"
:key="fault.id"
:fault="fault"
:is-selected="isSelected(fault)"
@toggle-selected="toggleSelected"
@acknowledge-selected="toggleAcknowledgeSelected"
@shelve-selected="toggleShelveSelected"
/>
</template>
</div>
</div>
</div>
</div>
</template>
<script>
import { FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS, FILTER_ITEMS, SORT_ITEMS } from './constants.js';
import FaultManagementListHeader from './FaultManagementListHeader.vue';
import FaultManagementListItem from './FaultManagementListItem.vue';
import FaultManagementSearch from './FaultManagementSearch.vue';
import FaultManagementToolbar from './FaultManagementToolbar.vue';
const SEARCH_KEYS = [
'id',
'triggerValueInfo',
'currentValueInfo',
'triggerTime',
'severity',
'name',
'shortDescription',
'namespace'
];
export default {
components: {
FaultManagementListHeader,
FaultManagementListItem,
FaultManagementSearch,
FaultManagementToolbar
},
inject: ['openmct', 'domainObject'],
props: {
faultsList: {
type: Array,
default: () => []
}
},
data() {
return {
filterIndex: 0,
searchTerm: '',
selectedFaults: {},
sortBy: Object.values(SORT_ITEMS)[0].value
};
},
computed: {
filteredFaultsList() {
const filterName = FILTER_ITEMS[this.filterIndex];
let list = this.faultsList;
// Exclude shelved alarms from all views except the Shelved view
if (filterName !== 'Shelved') {
list = list.filter((fault) => fault.shelved !== true);
}
if (filterName === 'Acknowledged') {
list = list.filter((fault) => fault.acknowledged);
} else if (filterName === 'Unacknowledged') {
list = list.filter((fault) => !fault.acknowledged);
} else if (filterName === 'Shelved') {
list = list.filter((fault) => fault.shelved);
}
if (this.searchTerm.length > 0) {
list = list.filter(this.filterUsingSearchTerm);
}
list.sort(SORT_ITEMS[this.sortBy].sortFunction);
return list;
},
showToolbar() {
return this.openmct.faults.supportsActions();
}
},
methods: {
filterUsingSearchTerm(fault) {
if (!fault) {
return false;
}
let match = false;
SEARCH_KEYS.forEach((key) => {
if (fault[key]?.toString().toLowerCase().includes(this.searchTerm)) {
match = true;
}
});
return match;
},
isSelected(fault) {
return Boolean(this.selectedFaults[fault.id]);
},
selectAll(toggle = false) {
this.faultsList.forEach((fault) => {
const faultData = {
fault,
selected: toggle
};
this.toggleSelected(faultData);
});
},
sortChanged(sort) {
this.sortBy = sort.value;
},
toggleSelected({ fault, selected = false }) {
if (selected) {
this.selectedFaults[fault.id] = fault;
} else {
delete this.selectedFaults[fault.id];
}
const selectedFaults = Object.values(this.selectedFaults);
this.openmct.selection.select(
[
{
element: this.$el,
context: {
item: this.openmct.router.path[0]
}
},
{
element: this.$el,
context: {
selectedFaults
}
}
],
false
);
},
toggleAcknowledgeSelected(faults = Object.values(this.selectedFaults)) {
let title = '';
if (faults.length > 1) {
title = `Acknowledge ${faults.length} selected faults`;
} else {
title = `Acknowledge fault: ${faults[0].name}`;
}
const formStructure = {
title,
sections: [
{
rows: [
{
key: 'comment',
control: 'textarea',
name: 'Optional comment',
pattern: '\\S+',
required: false,
cssClass: 'l-input-lg',
value: ''
}
]
}
],
buttons: {
submit: {
label: 'Acknowledge'
}
}
};
this.openmct.forms.showForm(formStructure).then((data) => {
Object.values(faults).forEach((selectedFault) => {
this.openmct.faults.acknowledgeFault(selectedFault, data);
});
});
this.selectedFaults = {};
},
async toggleShelveSelected(faults = Object.values(this.selectedFaults), shelveData = {}) {
const { shelved = true } = shelveData;
if (shelved) {
let title =
faults.length > 1
? `Shelve ${faults.length} selected faults`
: `Shelve fault: ${faults[0].name}`;
const formStructure = {
title,
sections: [
{
rows: [
{
key: 'comment',
control: 'textarea',
name: 'Optional comment',
pattern: '\\S+',
required: false,
cssClass: 'l-input-lg',
value: ''
},
{
key: 'shelveDuration',
control: 'select',
name: 'Shelve duration',
options: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS,
required: false,
cssClass: 'l-input-lg',
value: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS[0].value
}
]
}
],
buttons: {
submit: {
label: 'Shelve'
}
}
};
let data;
try {
data = await this.openmct.forms.showForm(formStructure);
} catch (e) {
return;
}
shelveData.comment = data.comment || '';
shelveData.shelveDuration =
data.shelveDuration !== undefined
? data.shelveDuration
: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS[0].value;
} else {
shelveData = {
shelved: false
};
}
Object.values(faults).forEach((selectedFault) => {
this.openmct.faults.shelveFault(selectedFault, shelveData);
});
this.selectedFaults = {};
},
updateFilter(filter) {
this.selectAll();
this.filterIndex = filter.model.options.findIndex((option) => option.value === filter.value);
},
updateSearchTerm(term = '') {
this.searchTerm = term.toLowerCase();
}
}
};
</script>

View File

@ -24,20 +24,22 @@
<div class="c-fault-mgmt__toolbar">
<button
class="c-icon-button icon-check"
title="Acknowledge selected faults"
:title="acknowledgeButtonLabel"
:aria-label="acknowledgeButtonLabel"
:disabled="disableAcknowledge"
@click="acknowledgeSelected"
>
<div title="Acknowledge selected faults" class="c-icon-button__label">Acknowledge</div>
<div class="c-icon-button__label">Acknowledge</div>
</button>
<button
class="c-icon-button icon-timer"
title="Shelve selected faults"
:title="shelveButtonLabel"
:aria-label="shelveButtonLabel"
:disabled="disableShelve"
@click="shelveSelected"
>
<div title="Shelve selected items" class="c-icon-button__label">Shelve</div>
<div class="c-icon-button__label">Shelve</div>
</button>
</div>
</template>
@ -60,6 +62,14 @@ export default {
disableShelve: true
};
},
computed: {
acknowledgeButtonLabel() {
return 'Acknowledge selected faults';
},
shelveButtonLabel() {
return 'Shelve selected faults';
}
},
watch: {
selectedFaults(newSelectedFaults) {
const selectedfaults = Object.values(newSelectedFaults);

View File

@ -21,23 +21,123 @@
-->
<template>
<FaultManagementListView :faults-list="faultsList" />
<div class="c-faults-list-view">
<FaultManagementSearch
:search-term="searchTerm"
@filter-changed="updateFilter"
@update-search-term="updateSearchTerm"
/>
<FaultManagementToolbar
v-if="showToolbar"
:selected-faults="selectedFaults"
@acknowledge-selected="toggleAcknowledgeSelected"
@shelve-selected="toggleShelveSelected"
/>
<div class="c-faults-list-view-header-item-container-wrapper">
<div class="c-faults-list-view-header-item-container">
<FaultManagementListHeader
class="header"
:selected-faults="selectedFaults"
:total-faults-count="filteredFaultsList.length"
@select-all="selectAll"
@sort-changed="sortChanged"
/>
<div class="c-faults-list-view-item-body">
<template v-if="filteredFaultsList.length > 0">
<FaultManagementListItem
v-for="fault of filteredFaultsList"
:key="fault.id"
:fault="fault"
:is-selected="isSelected(fault)"
@toggle-selected="toggleSelected"
@acknowledge-selected="toggleAcknowledgeSelected"
@shelve-selected="toggleShelveSelected"
@clear-all-selected="resetSelectedFaultMap"
/>
</template>
</div>
</div>
</div>
</div>
</template>
<script>
import { FAULT_MANAGEMENT_ALARMS, FAULT_MANAGEMENT_GLOBAL_ALARMS } from './constants.js';
import FaultManagementListView from './FaultManagementListView.vue';
import {
FAULT_MANAGEMENT_ALARMS,
FAULT_MANAGEMENT_GLOBAL_ALARMS,
FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS,
FILTER_ITEMS,
SORT_ITEMS
} from './constants.js';
import FaultManagementListHeader from './FaultManagementListHeader.vue';
import FaultManagementListItem from './FaultManagementListItem.vue';
import FaultManagementSearch from './FaultManagementSearch.vue';
import FaultManagementToolbar from './FaultManagementToolbar.vue';
const SEARCH_KEYS = [
'id',
'triggerValueInfo',
'currentValueInfo',
'triggerTime',
'severity',
'name',
'shortDescription',
'namespace'
];
export default {
components: {
FaultManagementListView
FaultManagementListHeader,
FaultManagementListItem,
FaultManagementSearch,
FaultManagementToolbar
},
inject: ['openmct', 'domainObject'],
data() {
return {
faultsList: []
faultsList: [],
filterIndex: 0,
searchTerm: '',
selectedFaultMap: {},
sortBy: Object.values(SORT_ITEMS)[0].value
};
},
computed: {
selectedFaults() {
return Object.values(this.selectedFaultMap);
},
filteredFaultsList() {
const filterName = FILTER_ITEMS[this.filterIndex];
let list = this.faultsList;
// Exclude shelved alarms from all views except the Shelved view
if (filterName !== 'Shelved') {
list = list.filter((fault) => fault.shelved !== true);
}
if (filterName === 'Acknowledged') {
list = list.filter((fault) => fault.acknowledged);
} else if (filterName === 'Unacknowledged') {
list = list.filter((fault) => !fault.acknowledged);
} else if (filterName === 'Shelved') {
list = list.filter((fault) => fault.shelved);
}
if (this.searchTerm.length > 0) {
list = list.filter(this.filterUsingSearchTerm);
}
list.sort(SORT_ITEMS[this.sortBy].sortFunction);
return list;
},
showToolbar() {
return this.openmct.faults.supportsActions();
}
},
mounted() {
this.unsubscribe = this.openmct.faults.subscribe(this.domainObject, this.updateFault);
},
@ -66,6 +166,181 @@ export default {
this.faultsList = [];
}
});
},
filterUsingSearchTerm(fault) {
if (!fault) {
return false;
}
let match = false;
SEARCH_KEYS.forEach((key) => {
if (fault[key]?.toString().toLowerCase().includes(this.searchTerm)) {
match = true;
}
});
return match;
},
isSelected(fault) {
return Boolean(this.selectedFaultMap[fault.id]);
},
selectAll(toggle = false) {
this.faultsList.forEach((fault) => {
const faultData = {
fault,
selected: toggle
};
this.toggleSelected(faultData);
});
},
sortChanged(sort) {
this.sortBy = sort.value;
},
toggleSelected({ fault, selected = false }) {
if (selected) {
this.selectedFaultMap[fault.id] = fault;
} else {
delete this.selectedFaultMap[fault.id];
}
this.openmct.selection.select(
[
{
element: this.$el,
context: {
item: this.openmct.router.path[0]
}
},
{
element: this.$el,
context: {
selectedFaults: this.selectedFaults
}
}
],
false
);
},
async toggleAcknowledgeSelected(faults = this.selectedFaults) {
let title = '';
if (faults.length > 1) {
title = `Acknowledge ${faults.length} selected faults`;
} else if (faults.length === 1) {
title = `Acknowledge fault: ${faults[0].name}`;
}
const formStructure = {
title,
sections: [
{
rows: [
{
key: 'comment',
control: 'textarea',
name: 'Optional comment',
pattern: '\\S+',
required: false,
cssClass: 'l-input-lg',
value: ''
}
]
}
],
buttons: {
submit: {
label: 'Acknowledge'
}
}
};
try {
const data = await this.openmct.forms.showForm(formStructure);
faults.forEach((fault) => {
this.openmct.faults.acknowledgeFault(fault, data);
});
} catch (err) {
console.error(err);
} finally {
this.resetSelectedFaultMap();
}
},
resetSelectedFaultMap() {
Object.keys(this.selectedFaultMap).forEach((key) => {
delete this.selectedFaultMap[key];
});
},
async toggleShelveSelected(faults = this.selectedFaults, shelveData = {}) {
const { shelved = true } = shelveData;
if (shelved) {
let title =
faults.length > 1
? `Shelve ${faults.length} selected faults`
: `Shelve fault: ${faults[0].name}`;
const formStructure = {
title,
sections: [
{
rows: [
{
key: 'comment',
control: 'textarea',
name: 'Optional comment',
pattern: '\\S+',
required: false,
cssClass: 'l-input-lg',
value: ''
},
{
key: 'shelveDuration',
control: 'select',
name: 'Shelve duration',
options: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS,
required: false,
cssClass: 'l-input-lg',
value: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS[0].value
}
]
}
],
buttons: {
submit: {
label: 'Shelve'
}
}
};
let data;
try {
data = await this.openmct.forms.showForm(formStructure);
} catch (e) {
return;
}
shelveData.comment = data.comment || '';
shelveData.shelveDuration =
data.shelveDuration !== undefined
? data.shelveDuration
: FAULT_MANAGEMENT_SHELVE_DURATIONS_IN_MS[0].value;
} else {
shelveData = {
shelved: false
};
}
Object.values(faults).forEach((selectedFault) => {
this.openmct.faults.shelveFault(selectedFault, shelveData);
});
this.selectedFaultMap = {};
},
updateFilter(filter) {
this.selectAll();
this.filterIndex = filter.model.options.findIndex((option) => option.value === filter.value);
},
updateSearchTerm(term = '') {
this.searchTerm = term.toLowerCase();
}
}
};