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 { fileURLToPath } from 'url';
import { expect } from '../pluginFixtures.js';
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultManagementWithExample(page) { export async function navigateToFaultManagementWithExample(page) {
await page.addInitScript({ await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProvider.js', import.meta.url)) path: fileURLToPath(new URL('./addInitExampleFaultProvider.js', import.meta.url))
}); });
@ -35,7 +37,7 @@ async function navigateToFaultManagementWithExample(page) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultManagementWithStaticExample(page) { export async function navigateToFaultManagementWithStaticExample(page) {
await page.addInitScript({ await page.addInitScript({
path: fileURLToPath(new URL('./addInitExampleFaultProviderStatic.js', import.meta.url)) path: fileURLToPath(new URL('./addInitExampleFaultProviderStatic.js', import.meta.url))
}); });
@ -46,7 +48,7 @@ async function navigateToFaultManagementWithStaticExample(page) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultManagementWithoutExample(page) { export async function navigateToFaultManagementWithoutExample(page) {
await page.addInitScript({ await page.addInitScript({
path: fileURLToPath(new URL('./addInitFaultManagementPlugin.js', import.meta.url)) path: fileURLToPath(new URL('./addInitFaultManagementPlugin.js', import.meta.url))
}); });
@ -57,7 +59,7 @@ async function navigateToFaultManagementWithoutExample(page) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function navigateToFaultItemInTree(page) { export async function navigateToFaultItemInTree(page) {
await page.goto('./', { waitUntil: 'networkidle' }); await page.goto('./', { waitUntil: 'networkidle' });
const faultManagementTreeItem = page const faultManagementTreeItem = page
@ -75,88 +77,95 @@ async function navigateToFaultItemInTree(page) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function acknowledgeFault(page, rowNumber) { export async function acknowledgeFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber); await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Acknowledge"').click(); await page.getByLabel('Acknowledge', { exact: true }).click();
// Click [aria-label="Save"] await page.getByLabel('Save').click();
await page.locator('[aria-label="Save"]').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function shelveMultipleFaults(page, ...nums) { export async function shelveMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => { const selectRows = nums.map((num) => {
return selectFaultItem(page, num); return selectFaultItem(page, num);
}); });
await Promise.all(selectRows); await Promise.all(selectRows);
await page.locator('button:has-text("Shelve")').click(); await page.getByLabel('Shelve selected faults').click();
await page.locator('[aria-label="Save"]').click(); await page.getByLabel('Save').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function acknowledgeMultipleFaults(page, ...nums) { export async function acknowledgeMultipleFaults(page, ...nums) {
const selectRows = nums.map((num) => { const selectRows = nums.map((num) => {
return selectFaultItem(page, num); return selectFaultItem(page, num);
}); });
await Promise.all(selectRows); await Promise.all(selectRows);
await page.locator('button:has-text("Acknowledge")').click(); 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 * @param {import('@playwright/test').Page} page
*/ */
async function shelveFault(page, rowNumber) { export async function shelveFault(page, rowNumber) {
await openFaultRowMenu(page, rowNumber); await openFaultRowMenu(page, rowNumber);
await page.locator('.c-menu >> text="Shelve"').click(); await page.locator('.c-menu >> text="Shelve"').click();
// Click [aria-label="Save"] // Click [aria-label="Save"]
await page.locator('[aria-label="Save"]').click(); await page.getByLabel('Save').click();
} }
/** /**
* @param {import('@playwright/test').Page} page * @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); await page.locator('.c-fault-mgmt__search-row select').first().selectOption(view);
} }
/** /**
* @param {import('@playwright/test').Page} page * @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); await page.locator('.c-fault-mgmt__list-header-sortButton select').selectOption(sort);
} }
/** /**
* @param {import('@playwright/test').Page} page * @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); await page.locator('.c-fault-mgmt-search [aria-label="Search Input"]').fill(term);
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function clearSearch(page) { export async function clearSearch(page) {
await enterSearchTerm(page, ''); await enterSearchTerm(page, '');
} }
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function selectFaultItem(page, rowNumber) { export async function selectFaultItem(page, rowNumber) {
await page.locator(`.c-fault-mgmt-item > input >> nth=${rowNumber - 1}`).check(); 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 * @param {import('@playwright/test').Page} page
*/ */
async function getHighestSeverity(page) { export async function getHighestSeverity(page) {
const criticalCount = await page.locator('[title=CRITICAL]').count(); const criticalCount = await page.locator('[title=CRITICAL]').count();
const warningCount = await page.locator('[title=WARNING]').count(); const warningCount = await page.locator('[title=WARNING]').count();
@ -172,7 +181,7 @@ async function getHighestSeverity(page) {
/** /**
* @param {import('@playwright/test').Page} 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 warningCount = await page.locator('[title=WARNING]').count();
const watchCount = await page.locator('[title=WATCH]').count(); const watchCount = await page.locator('[title=WATCH]').count();
@ -188,7 +197,7 @@ async function getLowestSeverity(page) {
/** /**
* @param {import('@playwright/test').Page} 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(); const count = await page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').count();
return count; return count;
@ -197,7 +206,7 @@ async function getFaultResultCount(page) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
function getFault(page, rowNumber) { export function getFault(page, rowNumber) {
const fault = page.locator( const fault = page.locator(
`.c-faults-list-view-item-body > .c-fault-mgmt__list >> nth=${rowNumber - 1}` `.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 * @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}")`); const fault = page.locator(`.c-fault-mgmt__list-faultname:has-text("${name}")`);
return fault; return fault;
@ -217,7 +226,7 @@ function getFaultByName(page, name) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultName(page, rowNumber) { export async function getFaultName(page, rowNumber) {
const faultName = await page const faultName = await page
.locator(`.c-fault-mgmt__list-faultname >> nth=${rowNumber - 1}`) .locator(`.c-fault-mgmt__list-faultname >> nth=${rowNumber - 1}`)
.textContent(); .textContent();
@ -228,7 +237,7 @@ async function getFaultName(page, rowNumber) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultSeverity(page, rowNumber) { export async function getFaultSeverity(page, rowNumber) {
const faultSeverity = await page const faultSeverity = await page
.locator(`.c-faults-list-view-item-body .c-fault-mgmt__list-severity >> nth=${rowNumber - 1}`) .locator(`.c-faults-list-view-item-body .c-fault-mgmt__list-severity >> nth=${rowNumber - 1}`)
.getAttribute('title'); .getAttribute('title');
@ -239,7 +248,7 @@ async function getFaultSeverity(page, rowNumber) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultNamespace(page, rowNumber) { export async function getFaultNamespace(page, rowNumber) {
const faultNamespace = await page const faultNamespace = await page
.locator(`.c-fault-mgmt__list-path >> nth=${rowNumber - 1}`) .locator(`.c-fault-mgmt__list-path >> nth=${rowNumber - 1}`)
.textContent(); .textContent();
@ -250,7 +259,7 @@ async function getFaultNamespace(page, rowNumber) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function getFaultTriggerTime(page, rowNumber) { export async function getFaultTriggerTime(page, rowNumber) {
const faultTriggerTime = await page const faultTriggerTime = await page
.locator(`.c-fault-mgmt__list-trigTime >> nth=${rowNumber - 1} >> .c-fault-mgmt-item__value`) .locator(`.c-fault-mgmt__list-trigTime >> nth=${rowNumber - 1} >> .c-fault-mgmt-item__value`)
.textContent(); .textContent();
@ -261,35 +270,10 @@ async function getFaultTriggerTime(page, rowNumber) {
/** /**
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
*/ */
async function openFaultRowMenu(page, rowNumber) { export async function openFaultRowMenu(page, rowNumber) {
// select // select
await page await page
.locator(`.c-fault-mgmt-item > .c-fault-mgmt__list-action-button >> nth=${rowNumber - 1}`) .getByLabel('Disposition actions')
.nth(rowNumber - 1)
.click(); .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. * 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'; import { expect, test } from '../../../../pluginFixtures.js';
test.describe('The Fault Management Plugin using example faults', () => { test.describe('The Fault Management Plugin using example faults', () => {
test.beforeEach(async ({ page }) => { 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 faultCount = await page.locator('c-fault-mgmt__list').count();
const criticalityIconCount = await page.locator('c-fault-mgmt__list-severity').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 page
}) => { }) => {
await utils.selectFaultItem(page, 1); await selectFaultItem(page, 1);
await page.getByRole('tab', { name: 'Config' }).click(); await page.getByRole('tab', { name: 'Config' }).click();
const selectedFaultName = await page const selectedFaultName = await page
@ -48,22 +69,22 @@ test.describe('The Fault Management Plugin using example faults', () => {
.locator(`.c-inspector__properties >> :text("${selectedFaultName}")`) .locator(`.c-inspector__properties >> :text("${selectedFaultName}")`)
.count(); .count();
await expect await expect(
.soft(page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first()) page.locator('.c-faults-list-view-item-body > .c-fault-mgmt__list').first()
.toHaveClass(/is-selected/); ).toHaveClass(/is-selected/);
expect.soft(inspectorFaultNameCount).toEqual(1); 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 page
}) => { }) => {
await utils.selectFaultItem(page, 1); await selectFaultItem(page, 1);
await utils.selectFaultItem(page, 2); await selectFaultItem(page, 2);
const selectedRows = page.locator( const selectedRows = page.locator(
'.c-fault-mgmt__list.is-selected .c-fault-mgmt__list-faultname' '.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(); await page.getByRole('tab', { name: 'Config' }).click();
const firstSelectedFaultName = await selectedRows.nth(0).textContent(); 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}")`) .locator(`.c-inspector__properties >> :text("${secondSelectedFaultName}")`)
.count(); .count();
expect.soft(firstNameInInspectorCount).toEqual(0); expect(firstNameInInspectorCount).toEqual(0);
expect.soft(secondNameInInspectorCount).toEqual(0); expect(secondNameInInspectorCount).toEqual(0);
}); });
test('Allows you to shelve a fault @unstable', async ({ page }) => { test('Allows you to shelve a fault', async ({ page }) => {
const shelvedFaultName = await utils.getFaultName(page, 2); const shelvedFaultName = await getFaultName(page, 2);
const beforeShelvedFault = utils.getFaultByName(page, shelvedFaultName); 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 // check it is removed from standard view
const afterShelvedFault = utils.getFaultByName(page, shelvedFaultName); const afterShelvedFault = getFaultByName(page, shelvedFaultName);
expect.soft(await afterShelvedFault.count()).toBe(0); 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 }) => { test('Allows you to acknowledge a fault', async ({ page }) => {
const acknowledgedFaultName = await utils.getFaultName(page, 3); const acknowledgedFaultName = await getFaultName(page, 3);
await utils.acknowledgeFault(page, 3); await acknowledgeFault(page, 3);
const fault = utils.getFault(page, 3); const fault = getFault(page, 3);
await expect.soft(fault).toHaveClass(/is-acknowledged/); await expect(fault).toHaveClass(/is-acknowledged/);
await utils.changeViewTo(page, 'acknowledged'); await changeViewTo(page, 'acknowledged');
const acknowledgedViewFaultName = await utils.getFaultName(page, 1); const acknowledgedViewFaultName = await getFaultName(page, 1);
expect.soft(acknowledgedFaultName).toEqual(acknowledgedViewFaultName); expect(acknowledgedFaultName).toEqual(acknowledgedViewFaultName);
}); });
test('Allows you to shelve multiple faults @unstable', async ({ page }) => { test('Allows you to shelve multiple faults', async ({ page }) => {
const shelvedFaultNameOne = await utils.getFaultName(page, 1); const shelvedFaultNameOne = await getFaultName(page, 1);
const shelvedFaultNameFour = await utils.getFaultName(page, 4); const shelvedFaultNameFour = await getFaultName(page, 4);
const beforeShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne); const beforeShelvedFaultOne = getFaultByName(page, shelvedFaultNameOne);
const beforeShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour); const beforeShelvedFaultFour = getFaultByName(page, shelvedFaultNameFour);
expect.soft(await beforeShelvedFaultOne.count()).toBe(1); await expect(beforeShelvedFaultOne).toHaveCount(1);
expect.soft(await beforeShelvedFaultFour.count()).toBe(1); await expect(beforeShelvedFaultFour).toHaveCount(1);
await utils.shelveMultipleFaults(page, 1, 4); await shelveMultipleFaults(page, 1, 4);
// check it is removed from standard view // check it is removed from standard view
const afterShelvedFaultOne = utils.getFaultByName(page, shelvedFaultNameOne); const afterShelvedFaultOne = getFaultByName(page, shelvedFaultNameOne);
const afterShelvedFaultFour = utils.getFaultByName(page, shelvedFaultNameFour); const afterShelvedFaultFour = getFaultByName(page, shelvedFaultNameFour);
expect.soft(await afterShelvedFaultOne.count()).toBe(0); await expect(afterShelvedFaultOne).toHaveCount(0);
expect.soft(await afterShelvedFaultFour.count()).toBe(0); await expect(afterShelvedFaultFour).toHaveCount(0);
await utils.changeViewTo(page, 'shelved'); await changeViewTo(page, 'shelved');
const shelvedViewFaultOne = utils.getFaultByName(page, shelvedFaultNameOne); const shelvedViewFaultOne = getFaultByName(page, shelvedFaultNameOne);
const shelvedViewFaultFour = utils.getFaultByName(page, shelvedFaultNameFour); const shelvedViewFaultFour = getFaultByName(page, shelvedFaultNameFour);
expect.soft(await shelvedViewFaultOne.count()).toBe(1); await expect(shelvedViewFaultOne).toHaveCount(1);
expect.soft(await shelvedViewFaultFour.count()).toBe(1); await expect(shelvedViewFaultFour).toHaveCount(1);
}); });
test('Allows you to acknowledge multiple faults @unstable', async ({ page }) => { test('Allows you to acknowledge multiple faults', async ({ page }) => {
const acknowledgedFaultNameTwo = await utils.getFaultName(page, 2); const acknowledgedFaultNameTwo = await getFaultName(page, 2);
const acknowledgedFaultNameFive = await utils.getFaultName(page, 5); const acknowledgedFaultNameFive = await getFaultName(page, 5);
await utils.acknowledgeMultipleFaults(page, 2, 5); await acknowledgeMultipleFaults(page, 2, 5);
const faultTwo = utils.getFault(page, 2); const faultTwo = getFault(page, 2);
const faultFive = utils.getFault(page, 5); const faultFive = getFault(page, 5);
// check they have been acknowledged // check they have been acknowledged
await expect.soft(faultTwo).toHaveClass(/is-acknowledged/); await expect(faultTwo).toHaveClass(/is-acknowledged/);
await expect.soft(faultFive).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 acknowledgedViewFaultTwo = getFaultByName(page, acknowledgedFaultNameTwo);
const acknowledgedViewFaultFive = utils.getFaultByName(page, acknowledgedFaultNameFive); const acknowledgedViewFaultFive = getFaultByName(page, acknowledgedFaultNameFive);
expect.soft(await acknowledgedViewFaultTwo.count()).toBe(1); await expect(acknowledgedViewFaultTwo).toHaveCount(1);
expect.soft(await acknowledgedViewFaultFive.count()).toBe(1); await expect(acknowledgedViewFaultFive).toHaveCount(1);
}); });
test('Allows you to search faults @unstable', async ({ page }) => { test('Allows you to search faults', async ({ page }) => {
const faultThreeNamespace = await utils.getFaultNamespace(page, 3); const faultThreeNamespace = await getFaultNamespace(page, 3);
const faultTwoName = await utils.getFaultName(page, 2); const faultTwoName = await getFaultName(page, 2);
const faultFiveTriggerTime = await utils.getFaultTriggerTime(page, 5); const faultFiveTriggerTime = await getFaultTriggerTime(page, 5);
// should be all faults (5) // should be all faults (5)
let faultResultCount = await utils.getFaultResultCount(page); let faultResultCount = await getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5); expect(faultResultCount).toEqual(5);
// search namespace // search namespace
await utils.enterSearchTerm(page, faultThreeNamespace); await enterSearchTerm(page, faultThreeNamespace);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1); expect(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultNamespace(page, 1)).toEqual(faultThreeNamespace); expect(await getFaultNamespace(page, 1)).toEqual(faultThreeNamespace);
// all faults // all faults
await utils.clearSearch(page); await clearSearch(page);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5); expect(faultResultCount).toEqual(5);
// search name // search name
await utils.enterSearchTerm(page, faultTwoName); await enterSearchTerm(page, faultTwoName);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1); expect(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultName(page, 1)).toEqual(faultTwoName); expect(await getFaultName(page, 1)).toEqual(faultTwoName);
// all faults // all faults
await utils.clearSearch(page); await clearSearch(page);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(5); expect(faultResultCount).toEqual(5);
// search triggerTime // search triggerTime
await utils.enterSearchTerm(page, faultFiveTriggerTime); await enterSearchTerm(page, faultFiveTriggerTime);
faultResultCount = await utils.getFaultResultCount(page); faultResultCount = await getFaultResultCount(page);
expect.soft(faultResultCount).toEqual(1); expect(faultResultCount).toEqual(1);
expect.soft(await utils.getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime); expect(await getFaultTriggerTime(page, 1)).toEqual(faultFiveTriggerTime);
}); });
test('Allows you to sort faults @unstable', async ({ page }) => { test('Allows you to sort faults', async ({ page }) => {
const highestSeverity = await utils.getHighestSeverity(page); const highestSeverity = await getHighestSeverity(page);
const lowestSeverity = await utils.getLowestSeverity(page); const lowestSeverity = await getLowestSeverity(page);
const faultOneName = 'Example Fault 1'; const faultOneName = 'Example Fault 1';
const faultFiveName = 'Example Fault 5'; 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); firstFaultName = await getFaultName(page, 1);
expect.soft(firstFaultName).toEqual(faultFiveName); expect(firstFaultName).toEqual(faultFiveName);
await utils.sortFaultsBy(page, 'severity'); await sortFaultsBy(page, 'severity');
const sortedHighestSeverity = await utils.getFaultSeverity(page, 1); const sortedHighestSeverity = await getFaultSeverity(page, 1);
const sortedLowestSeverity = await utils.getFaultSeverity(page, 5); const sortedLowestSeverity = await getFaultSeverity(page, 5);
expect.soft(sortedHighestSeverity).toEqual(highestSeverity); expect(sortedHighestSeverity).toEqual(highestSeverity);
expect.soft(sortedLowestSeverity).toEqual(lowestSeverity); expect(sortedLowestSeverity).toEqual(lowestSeverity);
}); });
}); });
test.describe('The Fault Management Plugin without using example faults', () => { test.describe('The Fault Management Plugin without using example faults', () => {
test.beforeEach(async ({ page }) => { 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(); 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(); 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(); 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 }) => { test('Will return no faults when searching', async ({ page }) => {
await utils.enterSearchTerm(page, 'fault'); await enterSearchTerm(page, 'fault');
const faultCount = await page.locator('c-fault-mgmt__list').count(); 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 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'; import { expect, test } from '../../pluginFixtures.js';
test.describe('Fault Management Visual Tests - without example', () => { test.describe('Fault Management Visual Tests - without example', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithoutExample(page); await navigateToFaultManagementWithoutExample(page);
await page.getByLabel('Collapse Inspect Pane').click(); await page.getByLabel('Collapse Inspect Pane').click();
await page.getByLabel('Click to collapse items').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.describe('Fault Management Visual Tests', () => {
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page }) => {
await utils.navigateToFaultManagementWithStaticExample(page); await navigateToFaultManagementWithStaticExample(page);
await page.getByLabel('Collapse Inspect Pane').click(); await page.getByLabel('Collapse Inspect Pane').click();
await page.getByLabel('Click to collapse items').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 }) => { test('fault list and acknowledged faults', async ({ page, theme }) => {
await percySnapshot(page, `Shows a list of faults in the standard view (theme: '${theme}')`); await percySnapshot(page, `Shows a list of faults in the standard view (theme: '${theme}')`);
await utils.acknowledgeFault(page, 1); await acknowledgeFault(page, 1);
await utils.changeViewTo(page, 'acknowledged'); await changeViewTo(page, 'acknowledged');
await percySnapshot( await percySnapshot(
page, page,
@ -73,12 +81,12 @@ test.describe('Fault Management Visual Tests', () => {
}); });
test('shelved faults', async ({ page, theme }) => { test('shelved faults', async ({ page, theme }) => {
await utils.shelveFault(page, 1); await shelveFault(page, 1);
await utils.changeViewTo(page, 'shelved'); await changeViewTo(page, 'shelved');
await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`); await percySnapshot(page, `Shelved faults appear in the shelved view (theme: '${theme}')`);
await utils.openFaultRowMenu(page, 1); await openFaultRowMenu(page, 1);
await percySnapshot( await percySnapshot(
page, page,
@ -87,7 +95,7 @@ test.describe('Fault Management Visual Tests', () => {
}); });
test('3-dot menu for fault', async ({ page, theme }) => { test('3-dot menu for fault', async ({ page, theme }) => {
await utils.openFaultRowMenu(page, 1); await openFaultRowMenu(page, 1);
await percySnapshot( await percySnapshot(
page, page,
@ -96,7 +104,7 @@ test.describe('Fault Management Visual Tests', () => {
}); });
test('ability to acknowledge or shelve', async ({ page, theme }) => { test('ability to acknowledge or shelve', async ({ page, theme }) => {
await utils.selectFaultItem(page, 1); await selectFaultItem(page, 1);
await percySnapshot( await percySnapshot(
page, page,

View File

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

View File

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

View File

@ -23,7 +23,7 @@
<template> <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__list-header c-fault-mgmt__list">
<div class="c-fault-mgmt-item-header c-fault-mgmt__checkbox"> <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>
<div <div
class="c-fault-mgmt-item-header c-fault-mgmt__list-header-results c-fault-mgmt__list-severity" class="c-fault-mgmt-item-header c-fault-mgmt__list-header-results c-fault-mgmt__list-severity"

View File

@ -23,7 +23,12 @@
<template> <template>
<div class="c-fault-mgmt__list data-selectable" :class="classesFromState"> <div class="c-fault-mgmt__list data-selectable" :class="classesFromState">
<div class="c-fault-mgmt-item c-fault-mgmt__list-checkbox"> <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>
<div class="c-fault-mgmt-item"> <div class="c-fault-mgmt-item">
<div <div
@ -60,6 +65,7 @@
<button <button
class="c-fault-mgmt__list-action-button l-browse-bar__actions c-icon-button icon-3-dots" class="c-fault-mgmt__list-action-button l-browse-bar__actions c-icon-button icon-3-dots"
title="Disposition Actions" title="Disposition Actions"
aria-label="Disposition Actions"
@click="showActionMenu" @click="showActionMenu"
></button> ></button>
</div> </div>
@ -86,13 +92,14 @@ export default {
}, },
isSelected: { isSelected: {
type: Boolean, type: Boolean,
default: () => { default: false
return false;
}
} }
}, },
emits: ['acknowledge-selected', 'shelve-selected', 'toggle-selected'], emits: ['acknowledge-selected', 'shelve-selected', 'toggle-selected', 'clear-all-selected'],
computed: { computed: {
checkBoxAriaLabel() {
return `Select fault: ${this.fault.name}`;
},
classesFromState() { classesFromState() {
const exclusiveStates = [ const exclusiveStates = [
{ {
@ -171,6 +178,7 @@ export default {
name: 'Acknowledge', name: 'Acknowledge',
description: '', description: '',
onItemClicked: (e) => { onItemClicked: (e) => {
this.clearAllSelected();
this.$emit('acknowledge-selected', [this.fault]); this.$emit('acknowledge-selected', [this.fault]);
} }
}, },
@ -179,6 +187,7 @@ export default {
name: 'Shelve', name: 'Shelve',
description: '', description: '',
onItemClicked: () => { onItemClicked: () => {
this.clearAllSelected();
this.$emit('shelve-selected', [this.fault], { shelved: true }); this.$emit('shelve-selected', [this.fault], { shelved: true });
} }
}, },
@ -188,6 +197,7 @@ export default {
name: 'Unshelve', name: 'Unshelve',
description: '', description: '',
onItemClicked: () => { onItemClicked: () => {
this.clearAllSelected();
this.$emit('shelve-selected', [this.fault], { shelved: false }); this.$emit('shelve-selected', [this.fault], { shelved: false });
} }
} }
@ -202,6 +212,9 @@ export default {
}; };
this.$emit('toggle-selected', faultData); 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"> <div class="c-fault-mgmt__toolbar">
<button <button
class="c-icon-button icon-check" class="c-icon-button icon-check"
title="Acknowledge selected faults" :title="acknowledgeButtonLabel"
:aria-label="acknowledgeButtonLabel"
:disabled="disableAcknowledge" :disabled="disableAcknowledge"
@click="acknowledgeSelected" @click="acknowledgeSelected"
> >
<div title="Acknowledge selected faults" class="c-icon-button__label">Acknowledge</div> <div class="c-icon-button__label">Acknowledge</div>
</button> </button>
<button <button
class="c-icon-button icon-timer" class="c-icon-button icon-timer"
title="Shelve selected faults" :title="shelveButtonLabel"
:aria-label="shelveButtonLabel"
:disabled="disableShelve" :disabled="disableShelve"
@click="shelveSelected" @click="shelveSelected"
> >
<div title="Shelve selected items" class="c-icon-button__label">Shelve</div> <div class="c-icon-button__label">Shelve</div>
</button> </button>
</div> </div>
</template> </template>
@ -60,6 +62,14 @@ export default {
disableShelve: true disableShelve: true
}; };
}, },
computed: {
acknowledgeButtonLabel() {
return 'Acknowledge selected faults';
},
shelveButtonLabel() {
return 'Shelve selected faults';
}
},
watch: { watch: {
selectedFaults(newSelectedFaults) { selectedFaults(newSelectedFaults) {
const selectedfaults = Object.values(newSelectedFaults); const selectedfaults = Object.values(newSelectedFaults);

View File

@ -21,23 +21,123 @@
--> -->
<template> <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> </template>
<script> <script>
import { FAULT_MANAGEMENT_ALARMS, FAULT_MANAGEMENT_GLOBAL_ALARMS } from './constants.js'; import {
import FaultManagementListView from './FaultManagementListView.vue'; 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 { export default {
components: { components: {
FaultManagementListView FaultManagementListHeader,
FaultManagementListItem,
FaultManagementSearch,
FaultManagementToolbar
}, },
inject: ['openmct', 'domainObject'], inject: ['openmct', 'domainObject'],
data() { data() {
return { 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() { mounted() {
this.unsubscribe = this.openmct.faults.subscribe(this.domainObject, this.updateFault); this.unsubscribe = this.openmct.faults.subscribe(this.domainObject, this.updateFault);
}, },
@ -66,6 +166,181 @@ export default {
this.faultsList = []; 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();
} }
} }
}; };