Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(#7394): Incorporate Status Indicators into the main Vue app #7395

Merged
merged 21 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/tests/functional/search.e2e.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ test.describe('Grand Search', () => {
page.waitForNavigation(),
page.getByLabel('OpenMCT Search').getByText('Clock A').click()
]);
await expect(page.getByRole('status', { name: 'Clock' })).toBeVisible();
await expect(page.getByRole('status', { name: 'Clock', exact: true })).toBeVisible();

await grandSearchInput.fill('Disp');
await expect(page.getByLabel('Object Search Result').first()).toContainText(
Expand Down
16 changes: 16 additions & 0 deletions e2e/tests/visual-a11y/components/header.visual.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ test.describe('Visual - Header @a11y', () => {
test.beforeEach(async ({ page }) => {
//Go to baseURL and Hide Tree
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
// Wait for status bar to load
await expect(
page.getByRole('status', {
name: 'Clock Indicator'
})
).toBeInViewport();
await expect(
page.getByRole('status', {
name: 'Global Clear Indicator'
})
).toBeInViewport();
await expect(
page.getByRole('status', {
name: 'Snapshot Indicator'
})
).toBeInViewport();
});

test('header sizing', async ({ page, theme }) => {
Expand Down
19 changes: 18 additions & 1 deletion e2e/tests/visual-a11y/faultManagement.visual.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import percySnapshot from '@percy/playwright';
import { fileURLToPath } from 'url';

import * as utils from '../../helper/faultUtils.js';
import { test } from '../../pluginFixtures.js';
import { expect, test } from '../../pluginFixtures.js';

test.describe('Fault Management Visual Tests', () => {
test('icon test', async ({ page, theme }) => {
Expand All @@ -32,6 +32,23 @@ test.describe('Fault Management Visual Tests', () => {
});
await page.goto('./', { waitUntil: 'domcontentloaded' });

// Wait for status bar to load
await expect(
page.getByRole('status', {
name: 'Clock Indicator'
})
).toBeInViewport();
await expect(
page.getByRole('status', {
name: 'Global Clear Indicator'
})
).toBeInViewport();
await expect(
page.getByRole('status', {
name: 'Snapshot Indicator'
})
).toBeInViewport();

await percySnapshot(page, `Fault Management icon appears in tree (theme: '${theme}')`);
});

Expand Down
24 changes: 24 additions & 0 deletions src/api/indicators/IndicatorAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@

import EventEmitter from 'EventEmitter';

import vueWrapHtmlElement from '../../utils/vueWrapHtmlElement.js';
import SimpleIndicator from './SimpleIndicator.js';

class IndicatorAPI extends EventEmitter {
/** @type {import('../../../openmct.js').OpenMCT} */
openmct;
constructor(openmct) {
super();

Expand All @@ -42,6 +45,18 @@ class IndicatorAPI extends EventEmitter {
return new SimpleIndicator(this.openmct);
}

/**
* @typedef {import('vue').Component} VueComponent
*/

/**
* @typedef {Object} Indicator
* @property {HTMLElement} [element]
* @property {VueComponent|Promise<VueComponent>} [vueComponent]
* @property {string} key
* @property {number} priority
*/

/**
* Accepts an indicator object, which is a simple object
* with a two attributes: 'element' which has an HTMLElement
Expand All @@ -62,11 +77,20 @@ class IndicatorAPI extends EventEmitter {
* myIndicator.text("Hello World!");
* myIndicator.iconClass("icon-info");
*
* If you would like to use a Vue component, you can pass it in
* directly as the 'vueComponent' attribute of the indicator object.
* This accepts a Vue component or a promise that resolves to a Vue component (for asynchronous
* rendering).
*
* @param {Indicator} indicator
*/
add(indicator) {
if (!indicator.priority) {
indicator.priority = this.openmct.priority.DEFAULT;
}
if (!indicator.vueComponent) {
indicator.vueComponent = vueWrapHtmlElement(indicator.element);
Copy link
Contributor

@shefalijoshi shefalijoshi Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the element property for indicator objects still exist after removing the mount utility? See here: https://github.com/nasa/openmct/blob/vue-indicators/src/plugins/clearData/plugin.js#L34

Perhaps you need to create the indicator.element node here as well?

Copy link
Contributor Author

@ozyx ozyx Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shefalijoshi Not for most of our default indicators. The idea is that if the vueComponent is provided to the indicator API, it will mount the component directly. Otherwise, it will look for the element property and wrap it in an anonymous Vue component, and then mount that. So one or the other is required here. If both vueComponent and element are missing, then we fail fast.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Add some documentation around this in the API change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are the existing docs ok? this just explains how to use the API, the implementation details shouldn't be important to the average user?

}

this.indicatorObjects.push(indicator);

Expand Down
40 changes: 31 additions & 9 deletions src/api/indicators/IndicatorAPISpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { defineComponent } from 'vue';

import { createOpenMct, resetApplicationState } from '../../utils/testing.js';
import SimpleIndicator from './SimpleIndicator.js';

Expand All @@ -33,7 +35,7 @@ describe('The Indicator API', () => {
return resetApplicationState(openmct);
});

function generateIndicator(className, label, priority) {
function generateHTMLIndicator(className, label, priority) {
const element = document.createElement('div');
element.classList.add(className);
const textNode = document.createTextNode(label);
Expand All @@ -46,46 +48,66 @@ describe('The Indicator API', () => {
return testIndicator;
}

it('can register an indicator', () => {
const testIndicator = generateIndicator('test-indicator', 'This is a test indicator', 2);
function generateVueIndicator(priority) {
return {
vueComponent: defineComponent({
template: '<div class="test-indicator">This is a test indicator</div>'
}),
priority
};
}

it('can register an HTML indicator', () => {
const testIndicator = generateHTMLIndicator('test-indicator', 'This is a test indicator', 2);
openmct.indicators.add(testIndicator);
expect(openmct.indicators.indicatorObjects).toBeDefined();
// notifier indicator is installed by default
expect(openmct.indicators.indicatorObjects.length).toBe(2);
});

it('can register a Vue indicator', () => {
const testIndicator = generateVueIndicator(2);
openmct.indicators.add(testIndicator);
expect(openmct.indicators.indicatorObjects).toBeDefined();
// notifier indicator is installed by default
expect(openmct.indicators.indicatorObjects.length).toBe(2);
});

it('can order indicators based on priority', () => {
const testIndicator1 = generateIndicator(
const testIndicator1 = generateHTMLIndicator(
'test-indicator-1',
'This is a test indicator',
openmct.priority.LOW
);
openmct.indicators.add(testIndicator1);

const testIndicator2 = generateIndicator(
const testIndicator2 = generateHTMLIndicator(
'test-indicator-2',
'This is another test indicator',
openmct.priority.DEFAULT
);
openmct.indicators.add(testIndicator2);

const testIndicator3 = generateIndicator(
const testIndicator3 = generateHTMLIndicator(
'test-indicator-3',
'This is yet another test indicator',
openmct.priority.LOW
);
openmct.indicators.add(testIndicator3);

const testIndicator4 = generateIndicator(
const testIndicator4 = generateHTMLIndicator(
'test-indicator-4',
'This is yet another test indicator',
openmct.priority.HIGH
);
openmct.indicators.add(testIndicator4);

expect(openmct.indicators.indicatorObjects.length).toBe(5);
const testIndicator5 = generateVueIndicator(openmct.priority.DEFAULT);
openmct.indicators.add(testIndicator5);

expect(openmct.indicators.indicatorObjects.length).toBe(6);
const indicatorObjectsByPriority = openmct.indicators.getIndicatorObjectsByPriority();
expect(indicatorObjectsByPriority.length).toBe(5);
expect(indicatorObjectsByPriority.length).toBe(6);
expect(indicatorObjectsByPriority[2].priority).toBe(openmct.priority.DEFAULT);
});

Expand Down
5 changes: 4 additions & 1 deletion src/plugins/clearData/components/GlobalClearIndicator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
at runtime from the About dialog for additional information.
-->
<template>
<div class="c-indicator c-indicator--clickable icon-clear-data s-status-caution">
<div
aria-label="Global Clear Indicator"
class="c-indicator c-indicator--clickable icon-clear-data s-status-caution"
>
<span class="label c-indicator__label">
<button @click="globalClearEmit">Clear Data</button>
</span>
Expand Down
23 changes: 2 additions & 21 deletions src/plugins/clearData/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import mount from 'utils/mount';

import ClearDataAction from './ClearDataAction.js';
import GlobalClearIndicator from './components/GlobalClearIndicator.vue';

Expand All @@ -31,27 +29,10 @@ export default function plugin(appliesToObjects, options = { indicator: true })

return function install(openmct) {
if (installIndicator) {
const { vNode, destroy } = mount(
{
components: {
GlobalClearIndicator
},
provide: {
openmct
},
template: '<GlobalClearIndicator></GlobalClearIndicator>'
},
{
app: openmct.app,
element: document.createElement('div')
}
);

let indicator = {
element: vNode.el,
vueComponent: GlobalClearIndicator,
key: 'global-clear-indicator',
priority: openmct.priority.DEFAULT,
destroy: destroy
priority: openmct.priority.DEFAULT
};

openmct.indicators.add(indicator);
Expand Down
11 changes: 3 additions & 8 deletions src/plugins/clearData/pluginSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
*****************************************************************************/

import { createMouseEvent, createOpenMct, resetApplicationState } from 'utils/testing';
import { nextTick } from 'vue';

import ClearDataPlugin from './plugin.js';

Expand Down Expand Up @@ -208,12 +207,11 @@ describe('The Clear Data Plugin:', () => {
it('installs', () => {
const globalClearIndicator = openmct.indicators.indicatorObjects.find(
(indicator) => indicator.key === 'global-clear-indicator'
).element;
).vueComponent;
expect(globalClearIndicator).toBeDefined();
});

it('renders its major elements', async () => {
await nextTick();
it('renders its major elements', () => {
const indicatorClass = appHolder.querySelector('.c-indicator');
const iconClass = appHolder.querySelector('.icon-clear-data');
const indicatorLabel = appHolder.querySelector('.c-indicator__label');
Expand All @@ -228,10 +226,7 @@ describe('The Clear Data Plugin:', () => {
const indicatorLabel = appHolder.querySelector('.c-indicator__label');
const buttonElement = indicatorLabel.querySelector('button');
const clickEvent = createMouseEvent('click');
openmct.objectViews.on('clearData', () => {
// when we click the button, this event should fire
done();
});
openmct.objectViews.on('clearData', done);
buttonElement.dispatchEvent(clickEvent);
});
});
Expand Down
18 changes: 12 additions & 6 deletions src/plugins/clock/components/ClockIndicator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<template>
<div
aria-label="Clock Indicator"
class="c-indicator t-indicator-clock icon-clock no-minify c-indicator--not-clickable"
role="complementary"
>
Expand All @@ -40,27 +41,32 @@ export default {
props: {
indicatorFormat: {
type: String,
required: true
default: 'YYYY/MM/DD HH:mm:ss'
}
},
data() {
return {
timeTextValue: this.openmct.time.getClock() ? this.openmct.time.now() : undefined
timestamp: this.openmct.time.getClock() ? this.openmct.time.now() : undefined
};
},
computed: {
timeTextValue() {
return `${moment.utc(this.timestamp).format(this.indicatorFormat)} ${
this.openmct.time.getTimeSystem().name
}`;
}
},
mounted() {
this.tick = raf(this.tick);
this.openmct.time.on('tick', this.tick);
this.tick(this.timeTextValue);
this.tick(this.timestamp);
},
beforeUnmount() {
this.openmct.time.off('tick', this.tick);
},
methods: {
tick(timestamp) {
this.timeTextValue = `${moment.utc(timestamp).format(this.indicatorFormat)} ${
this.openmct.time.getTimeSystem().name
}`;
this.timestamp = timestamp;
}
}
};
Expand Down
28 changes: 2 additions & 26 deletions src/plugins/clock/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
*****************************************************************************/

import momentTimezone from 'moment-timezone';
import mount from 'utils/mount';

import ClockViewProvider from './ClockViewProvider.js';
import ClockIndicator from './components/ClockIndicator.vue';

export default function ClockPlugin(options) {
return function install(openmct) {
const CLOCK_INDICATOR_FORMAT = 'YYYY/MM/DD HH:mm:ss';
openmct.types.addType('clock', {
name: 'Clock',
description:
Expand Down Expand Up @@ -92,31 +90,9 @@ export default function ClockPlugin(options) {
});
openmct.objectViews.addProvider(new ClockViewProvider(openmct));

if (options && options.enableClockIndicator === true) {
const element = document.createElement('div');

const { vNode } = mount(
{
components: {
ClockIndicator
},
provide: {
openmct
},
data() {
return {
indicatorFormat: CLOCK_INDICATOR_FORMAT
};
},
template: '<ClockIndicator :indicator-format="indicatorFormat" />'
},
{
app: openmct.app,
element
}
);
if (options?.enableClockIndicator === true) {
const indicator = {
element: vNode.el,
vueComponent: ClockIndicator,
key: 'clock-indicator',
priority: openmct.priority.LOW
};
Expand Down
Loading
Loading