Role selection for operator status roles (#6706)

* Add additional test roles to example user

* Add session storage and role to user indicator

* Update example user provider

* Added selection dialog to overlays and implemented for operator status

* Display role in user indicator

* Updates to broadcast channel lifecycle

* Update comment

* Comment width

* UserAPI role updates and UserIndicator improvement

* Moved prompt to UserIndicator

* Reconnect channel on error and UserIndicator updates

* Updates to status api canPRovideStatusForRole

* Cleanup

* Store status roles in an array instead of a singular value

* Added success notification and cleanup

* Lint

* Removed unused role param from status api call

* Remove default status role from example user plugin

* Removed status.getStatusRoleForCurrentUser

* Cleanup

* Cleanup

* Moved roleChannel to private field

* Separated input value from active role value

* More flight like status role names and parameter names

* Update statusRole parameter name

* Update default selection for roles if input is not chosen

* Update OperatorStatusIndicator install to hide if an observer

* console.log

* Return null instead of undefined

* Remove unneccesary filter on allRoles

* refactor: format with prettier

* Undid merge error

* Merge conflict extra line

* Copyright statement

* RoleChannelProvider to RoleChannel

* Throw error on no provider

* Change RoleChannel to ActiveRoleSynchronizer and update method calls to match

* iconClass to alert

* Add role selection step to beforeEach

* example-role to flight

* Dismiss overlay from exampleUser plugin which affected menu api positioning

---------

Co-authored-by: Scott Bell <scott@traclabs.com>
This commit is contained in:
Michael Rogers 2023-07-14 14:10:58 -05:00 committed by GitHub
parent 92329b3d8e
commit 32529ff6b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 434 additions and 70 deletions

View File

@ -47,6 +47,11 @@ test.describe('Operator Status', () => {
path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js') path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js')
}); });
await page.goto('./', { waitUntil: 'domcontentloaded' }); await page.goto('./', { waitUntil: 'domcontentloaded' });
await expect(page.getByText('Select Role')).toBeVisible();
// set role
await page.getByRole('button', { name: 'Select' }).click();
// dismiss role confirmation popup
await page.getByRole('button', { name: 'Dismiss' }).click();
}); });
// verify that operator status is visible // verify that operator status is visible

View File

@ -63,16 +63,24 @@ const STATUSES = [
* @implements {StatusUserProvider} * @implements {StatusUserProvider}
*/ */
export default class ExampleUserProvider extends EventEmitter { export default class ExampleUserProvider extends EventEmitter {
constructor(openmct, { defaultStatusRole } = { defaultStatusRole: undefined }) { constructor(
openmct,
{ statusRoles } = {
statusRoles: []
}
) {
super(); super();
this.openmct = openmct; this.openmct = openmct;
this.user = undefined; this.user = undefined;
this.loggedIn = false; this.loggedIn = false;
this.autoLoginUser = undefined; this.autoLoginUser = undefined;
this.status = STATUSES[0]; this.statusRoleValues = statusRoles.map((role) => ({
role: role,
status: STATUSES[0]
}));
this.pollQuestion = undefined; this.pollQuestion = undefined;
this.defaultStatusRole = defaultStatusRole; this.statusRoles = statusRoles;
this.ExampleUser = createExampleUser(this.openmct.user.User); this.ExampleUser = createExampleUser(this.openmct.user.User);
this.loginPromise = undefined; this.loginPromise = undefined;
@ -94,14 +102,13 @@ export default class ExampleUserProvider extends EventEmitter {
return this.loginPromise; return this.loginPromise;
} }
canProvideStatusForRole() { canProvideStatusForRole(role) {
return Promise.resolve(true); return this.statusRoles.includes(role);
} }
canSetPollQuestion() { canSetPollQuestion() {
return Promise.resolve(true); return Promise.resolve(true);
} }
hasRole(roleId) { hasRole(roleId) {
if (!this.loggedIn) { if (!this.loggedIn) {
Promise.resolve(undefined); Promise.resolve(undefined);
@ -110,16 +117,18 @@ export default class ExampleUserProvider extends EventEmitter {
return Promise.resolve(this.user.getRoles().includes(roleId)); return Promise.resolve(this.user.getRoles().includes(roleId));
} }
getStatusRoleForCurrentUser() { getPossibleRoles() {
return Promise.resolve(this.defaultStatusRole); return this.user.getRoles();
} }
getAllStatusRoles() { getAllStatusRoles() {
return Promise.resolve([this.defaultStatusRole]); return Promise.resolve(this.statusRoles);
} }
getStatusForRole(role) { getStatusForRole(role) {
return Promise.resolve(this.status); const statusForRole = this.statusRoleValues.find((statusRole) => statusRole.role === role);
return Promise.resolve(statusForRole?.status);
} }
async getDefaultStatusForRole(role) { async getDefaultStatusForRole(role) {
@ -130,7 +139,8 @@ export default class ExampleUserProvider extends EventEmitter {
setStatusForRole(role, status) { setStatusForRole(role, status) {
status.timestamp = Date.now(); status.timestamp = Date.now();
this.status = status; const matchingIndex = this.statusRoleValues.findIndex((statusRole) => statusRole.role === role);
this.statusRoleValues[matchingIndex].status = status;
this.emit('statusChange', { this.emit('statusChange', {
role, role,
status status
@ -175,7 +185,7 @@ export default class ExampleUserProvider extends EventEmitter {
// for testing purposes, this will skip the form, this wouldn't be used in // for testing purposes, this will skip the form, this wouldn't be used in
// a normal authentication process // a normal authentication process
if (this.autoLoginUser) { if (this.autoLoginUser) {
this.user = new this.ExampleUser(id, this.autoLoginUser, ['example-role']); this.user = new this.ExampleUser(id, this.autoLoginUser, ['flight', 'driver', 'observer']);
this.loggedIn = true; this.loggedIn = true;
return Promise.resolve(); return Promise.resolve();

View File

@ -21,16 +21,18 @@
*****************************************************************************/ *****************************************************************************/
import ExampleUserProvider from './ExampleUserProvider'; import ExampleUserProvider from './ExampleUserProvider';
const AUTO_LOGIN_USER = 'mct-user';
const STATUS_ROLES = ['flight', 'driver'];
export default function ExampleUserPlugin( export default function ExampleUserPlugin(
{ autoLoginUser, defaultStatusRole } = { { autoLoginUser, statusRoles } = {
autoLoginUser: 'guest', autoLoginUser: AUTO_LOGIN_USER,
defaultStatusRole: 'test-role' statusRoles: STATUS_ROLES
} }
) { ) {
return function install(openmct) { return function install(openmct) {
const userProvider = new ExampleUserProvider(openmct, { const userProvider = new ExampleUserProvider(openmct, {
defaultStatusRole statusRoles
}); });
if (autoLoginUser !== undefined) { if (autoLoginUser !== undefined) {

View File

@ -1,6 +1,29 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Overlay from './Overlay'; import Overlay from './Overlay';
import Dialog from './Dialog'; import Dialog from './Dialog';
import ProgressDialog from './ProgressDialog'; import ProgressDialog from './ProgressDialog';
import Selection from './Selection';
/** /**
* The OverlayAPI is responsible for pre-pending templates to * The OverlayAPI is responsible for pre-pending templates to
@ -130,6 +153,13 @@ class OverlayAPI {
return progressDialog; return progressDialog;
} }
selection(options) {
let selection = new Selection(options);
this.showOverlay(selection);
return selection;
}
} }
export default OverlayAPI; export default OverlayAPI;

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import SelectionComponent from './components/SelectionComponent.vue';
import Overlay from './Overlay';
import Vue from 'vue';
class Selection extends Overlay {
constructor({
iconClass,
title,
message,
selectionOptions,
onChange,
currentSelection,
...options
}) {
let component = new Vue({
components: {
SelectionComponent: SelectionComponent
},
provide: {
iconClass,
title,
message,
selectionOptions,
onChange,
currentSelection
},
template: '<selection-component></selection-component>'
}).$mount();
super({
element: component.$el,
size: 'fit',
dismissable: false,
onChange,
currentSelection,
...options
});
this.once('destroy', () => {
component.$destroy();
});
}
}
export default Selection;

View File

@ -0,0 +1,34 @@
<template>
<div class="c-message">
<!--Uses flex-row -->
<div class="c-message__icon" :class="['u-icon-bg-color-' + iconClass]"></div>
<div class="c-message__text">
<!-- Uses flex-column -->
<div v-if="title" class="c-message__title">
{{ title }}
</div>
<div v-if="message" class="c-message__action-text">
{{ message }}
</div>
<select @change="onChange">
<option
v-for="option in selectionOptions"
:key="option.key"
:value="option.key"
:selected="option.key === currentSelection"
>
{{ option.name }}
</option>
</select>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
inject: ['iconClass', 'title', 'message', 'selectionOptions', 'currentSelection', 'onChange']
};
</script>

View File

@ -0,0 +1,37 @@
import { ACTIVE_ROLE_BROADCAST_CHANNEL_NAME } from './constants';
class ActiveRoleSynchronizer {
#roleChannel;
constructor(openmct) {
this.openmct = openmct;
this.#roleChannel = new BroadcastChannel(ACTIVE_ROLE_BROADCAST_CHANNEL_NAME);
this.setActiveRoleFromChannelMessage = this.setActiveRoleFromChannelMessage.bind(this);
this.subscribeToRoleChanges(this.setActiveRoleFromChannelMessage);
}
subscribeToRoleChanges(callback) {
this.#roleChannel.addEventListener('message', callback);
}
unsubscribeFromRoleChanges(callback) {
this.#roleChannel.removeEventListener('message', callback);
}
setActiveRoleFromChannelMessage(event) {
const role = event.data;
this.openmct.user.setActiveRole(role);
}
broadcastNewRole(role) {
if (!this.#roleChannel.name) {
return false;
}
this.#roleChannel.postMessage(role);
}
destroy() {
this.unsubscribeFromRoleChanges(this.setActiveRoleFromChannelMessage);
this.#roleChannel.close();
}
}
export default ActiveRoleSynchronizer;

View File

@ -140,9 +140,9 @@ export default class StatusAPI extends EventEmitter {
const provider = this.#userAPI.getProvider(); const provider = this.#userAPI.getProvider();
if (provider.canProvideStatusForRole) { if (provider.canProvideStatusForRole) {
return provider.canProvideStatusForRole(role); return Promise.resolve(provider.canProvideStatusForRole(role));
} else { } else {
return false; return Promise.resolve(false);
} }
} }
@ -151,11 +151,16 @@ export default class StatusAPI extends EventEmitter {
* @param {Status} status The status to set for the provided role * @param {Status} status The status to set for the provided role
* @returns {Promise<Boolean>} true if operation was successful, otherwise false. * @returns {Promise<Boolean>} true if operation was successful, otherwise false.
*/ */
setStatusForRole(role, status) { setStatusForRole(status) {
const provider = this.#userAPI.getProvider(); const provider = this.#userAPI.getProvider();
if (provider.setStatusForRole) { if (provider.setStatusForRole) {
return provider.setStatusForRole(role, status); const activeRole = this.#userAPI.getActiveRole();
if (!provider.canProvideStatusForRole(activeRole)) {
return false;
}
return provider.setStatusForRole(activeRole, status);
} else { } else {
this.#userAPI.error('User provider does not support setting role status'); this.#userAPI.error('User provider does not support setting role status');
} }
@ -216,21 +221,6 @@ export default class StatusAPI extends EventEmitter {
} }
} }
/**
* The status role of the current user. A user may have multiple roles, but will only have one role
* that provides status at any time.
* @returns {Promise<import("./UserAPI").Role>} the role for which the current user can provide status.
*/
getStatusRoleForCurrentUser() {
const provider = this.#userAPI.getProvider();
if (provider.getStatusRoleForCurrentUser) {
return provider.getStatusRoleForCurrentUser();
} else {
this.#userAPI.error('User provider cannot provide role status for this user');
}
}
/** /**
* @returns {Promise<Boolean>} true if the configured UserProvider can provide status for the currently logged in user, false otherwise. * @returns {Promise<Boolean>} true if the configured UserProvider can provide status for the currently logged in user, false otherwise.
* @see StatusUserProvider * @see StatusUserProvider
@ -238,14 +228,13 @@ export default class StatusAPI extends EventEmitter {
async canProvideStatusForCurrentUser() { async canProvideStatusForCurrentUser() {
const provider = this.#userAPI.getProvider(); const provider = this.#userAPI.getProvider();
if (provider.getStatusRoleForCurrentUser) { if (!provider) {
const activeStatusRole = await this.#userAPI.getProvider().getStatusRoleForCurrentUser();
const canProvideStatus = await this.canProvideStatusForRole(activeStatusRole);
return canProvideStatus;
} else {
return false; return false;
} }
const activeStatusRole = await this.#userAPI.getActiveRole();
const canProvideStatus = await this.canProvideStatusForRole(activeStatusRole);
return canProvideStatus;
} }
/** /**

View File

@ -77,5 +77,4 @@ export default class StatusUserProvider extends UserProvider {
/** /**
* @returns {Promise<import("./UserAPI").Role>} the active status role for the currently logged in user * @returns {Promise<import("./UserAPI").Role>} the active status role for the currently logged in user
*/ */
async getStatusRoleForCurrentUser() {}
} }

View File

@ -0,0 +1,37 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { ACTIVE_ROLE_LOCAL_STORAGE_KEY } from './constants';
class StoragePersistance {
getActiveRole() {
return localStorage.getItem(ACTIVE_ROLE_LOCAL_STORAGE_KEY);
}
setActiveRole(role) {
return localStorage.setItem(ACTIVE_ROLE_LOCAL_STORAGE_KEY, role);
}
clearActiveRole() {
return localStorage.removeItem(ACTIVE_ROLE_LOCAL_STORAGE_KEY);
}
}
export default new StoragePersistance();

View File

@ -24,6 +24,7 @@ import EventEmitter from 'EventEmitter';
import { MULTIPLE_PROVIDER_ERROR, NO_PROVIDER_ERROR } from './constants'; import { MULTIPLE_PROVIDER_ERROR, NO_PROVIDER_ERROR } from './constants';
import StatusAPI from './StatusAPI'; import StatusAPI from './StatusAPI';
import User from './User'; import User from './User';
import StoragePersistance from './StoragePersistance';
class UserAPI extends EventEmitter { class UserAPI extends EventEmitter {
/** /**
@ -86,6 +87,58 @@ class UserAPI extends EventEmitter {
return this._provider.getCurrentUser(); return this._provider.getCurrentUser();
} }
} }
/**
* If a user provider is set, it will return an array of possible roles
* that can be selected by the current user
* @memberof module:openmct.UserAPI#
* @returns {Array}
* @throws Will throw an error if no user provider is set
*/
getPossibleRoles() {
if (!this.hasProvider()) {
this.error(NO_PROVIDER_ERROR);
}
return this._provider.getPossibleRoles();
}
/**
* If a user provider is set, it will return the active role or null
* @memberof module:openmct.UserAPI#
* @returns {string|null}
*/
getActiveRole() {
if (!this.hasProvider()) {
return null;
}
// get from session storage
const sessionStorageValue = StoragePersistance.getActiveRole();
return sessionStorageValue;
}
/**
* Set the active role in session storage
* @memberof module:openmct.UserAPI#
* @returns {undefined}
*/
setActiveRole(role) {
StoragePersistance.setActiveRole(role);
this.emit('roleChanged', role);
}
/**
* Will return if a role can provide a operator status response
* @memberof module:openmct.UserApi#
* @returns {Boolean}
*/
canProvideStatusForRole() {
if (!this.hasProvider()) {
return null;
}
const activeRole = this.getActiveRole();
return this._provider.canProvideStatusForRole?.(activeRole);
}
/** /**
* If a user provider is set, it will return the user provider's * If a user provider is set, it will return the user provider's

View File

@ -25,7 +25,7 @@ import { MULTIPLE_PROVIDER_ERROR } from './constants';
import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider'; import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider';
const USERNAME = 'Test User'; const USERNAME = 'Test User';
const EXAMPLE_ROLE = 'example-role'; const EXAMPLE_ROLE = 'flight';
describe('The User API', () => { describe('The User API', () => {
let openmct; let openmct;

View File

@ -22,3 +22,6 @@
export const MULTIPLE_PROVIDER_ERROR = 'Only one user provider may be set at a time.'; export const MULTIPLE_PROVIDER_ERROR = 'Only one user provider may be set at a time.';
export const NO_PROVIDER_ERROR = 'No user provider has been set.'; export const NO_PROVIDER_ERROR = 'No user provider has been set.';
export const ACTIVE_ROLE_LOCAL_STORAGE_KEY = 'ACTIVE_USER_ROLE';
export const ACTIVE_ROLE_BROADCAST_CHANNEL_NAME = 'ActiveRoleChannel';

View File

@ -48,7 +48,6 @@
<script> <script>
const DEFAULT_POLL_QUESTION = 'NO POLL QUESTION'; const DEFAULT_POLL_QUESTION = 'NO POLL QUESTION';
export default { export default {
inject: ['openmct', 'indicator', 'configuration'], inject: ['openmct', 'indicator', 'configuration'],
props: { props: {
@ -63,7 +62,6 @@ export default {
}, },
data() { data() {
return { return {
allRoles: [],
role: '--', role: '--',
pollQuestionUpdated: '--', pollQuestionUpdated: '--',
currentPollQuestion: DEFAULT_POLL_QUESTION, currentPollQuestion: DEFAULT_POLL_QUESTION,
@ -78,26 +76,27 @@ export default {
left: `${this.positionX}px`, left: `${this.positionX}px`,
top: `${this.positionY}px` top: `${this.positionY}px`
}; };
},
canProvideStatusForRole() {
return this.openmct.user.canProvideStatusForRole(this.role);
} }
}, },
beforeDestroy() { beforeDestroy() {
this.openmct.user.status.off('statusChange', this.setStatus); this.openmct.user.status.off('statusChange', this.setStatus);
this.openmct.user.status.off('pollQuestionChange', this.setPollQuestion); this.openmct.user.status.off('pollQuestionChange', this.setPollQuestion);
this.openmct.user.off('roleChanged', this.fetchMyStatus);
}, },
async mounted() { async mounted() {
this.unsubscribe = []; this.unsubscribe = [];
await this.fetchUser(); await this.fetchUser();
await this.findFirstApplicableRole();
this.fetchPossibleStatusesForUser(); this.fetchPossibleStatusesForUser();
this.fetchCurrentPoll(); this.fetchCurrentPoll();
this.fetchMyStatus(); await this.fetchMyStatus();
this.subscribeToMyStatus(); this.subscribeToMyStatus();
this.subscribeToPollQuestion(); this.subscribeToPollQuestion();
this.subscribeToRoleChange();
}, },
methods: { methods: {
async findFirstApplicableRole() {
this.role = await this.openmct.user.status.getStatusRoleForCurrentUser();
},
async fetchUser() { async fetchUser() {
this.user = await this.openmct.user.getCurrentUser(); this.user = await this.openmct.user.getCurrentUser();
}, },
@ -117,9 +116,22 @@ export default {
this.indicator.text(pollQuestion?.question || ''); this.indicator.text(pollQuestion?.question || '');
}, },
async fetchMyStatus() { async fetchMyStatus() {
const activeStatusRole = await this.openmct.user.status.getStatusRoleForCurrentUser(); // hide indicator for observer
const status = await this.openmct.user.status.getStatusForRole(activeStatusRole); const isStatusCapable = await this.openmct.user.canProvideStatusForRole();
if (!isStatusCapable) {
this.indicator.text('');
this.indicator.statusClass('hidden');
return;
}
const activeRole = await this.openmct.user.getActiveRole();
if (!activeRole) {
return;
}
this.role = activeRole;
const status = await this.openmct.user.status.getStatusForRole(activeRole);
if (status !== undefined) { if (status !== undefined) {
this.setStatus({ status }); this.setStatus({ status });
} }
@ -130,7 +142,10 @@ export default {
subscribeToPollQuestion() { subscribeToPollQuestion() {
this.openmct.user.status.on('pollQuestionChange', this.setPollQuestion); this.openmct.user.status.on('pollQuestionChange', this.setPollQuestion);
}, },
setStatus({ role, status }) { subscribeToRoleChange() {
this.openmct.user.on('roleChanged', this.fetchMyStatus);
},
setStatus({ status }) {
status = this.applyStyling(status); status = this.applyStyling(status);
this.selectedStatus = status.key; this.selectedStatus = status.key;
this.indicator.iconClass(status.iconClassPoll); this.indicator.iconClass(status.iconClassPoll);
@ -148,11 +163,16 @@ export default {
return this.allStatuses.find((possibleMatch) => possibleMatch.key === statusKey); return this.allStatuses.find((possibleMatch) => possibleMatch.key === statusKey);
}, },
async changeStatus() { async changeStatus() {
if (!this.openmct.user.canProvideStatusForRole()) {
this.openmct.notifications.error('Selected role is ineligible to provide operator status');
return;
}
if (this.selectedStatus !== undefined) { if (this.selectedStatus !== undefined) {
const statusObject = this.findStatusByKey(this.selectedStatus); const statusObject = this.findStatusByKey(this.selectedStatus);
const result = await this.openmct.user.status.setStatusForRole(this.role, statusObject); const result = await this.openmct.user.status.setStatusForRole(statusObject);
if (result === true) { if (result === true) {
this.openmct.notifications.info('Successfully set operator status'); this.openmct.notifications.info('Successfully set operator status');
} else { } else {

View File

@ -29,13 +29,8 @@ import PollQuestionIndicator from './pollQuestion/PollQuestionIndicator';
export default function operatorStatusPlugin(configuration) { export default function operatorStatusPlugin(configuration) {
return function install(openmct) { return function install(openmct) {
if (openmct.user.hasProvider()) { if (openmct.user.hasProvider()) {
openmct.user.status.canProvideStatusForCurrentUser().then((canProvideStatus) => { const operatorStatusIndicator = new OperatorStatusIndicator(openmct, configuration);
if (canProvideStatus) { operatorStatusIndicator.install();
const operatorStatusIndicator = new OperatorStatusIndicator(openmct, configuration);
operatorStatusIndicator.install();
}
});
openmct.user.status.canSetPollQuestion().then((canSetPollQuestion) => { openmct.user.status.canSetPollQuestion().then((canSetPollQuestion) => {
if (canSetPollQuestion) { if (canSetPollQuestion) {

View File

@ -23,30 +23,107 @@
<template> <template>
<div class="c-indicator icon-person c-indicator--clickable"> <div class="c-indicator icon-person c-indicator--clickable">
<span class="label c-indicator__label"> <span class="label c-indicator__label">
{{ userName }} {{ role ? `${userName}: ${role}` : userName }}
<button @click="promptForRoleSelection">Change Role</button>
</span> </span>
</div> </div>
</template> </template>
<script> <script>
import ActiveRoleSynchronizer from '../../../api/user/ActiveRoleSynchronizer';
export default { export default {
inject: ['openmct'], inject: ['openmct'],
data() { data() {
return { return {
userName: undefined, userName: undefined,
loggedIn: false role: undefined,
loggedIn: false,
roleChannel: undefined,
inputRoleSelection: undefined,
roleSelectionDialog: undefined
}; };
}, },
mounted() { async mounted() {
this.getUserInfo(); await this.getUserInfo();
this.roleChannel = new ActiveRoleSynchronizer(this.openmct);
this.roleChannel.subscribeToRoleChanges(this.onRoleChange);
await this.fetchOrPromptForRole();
},
beforeDestroy() {
this.roleChannel.unsubscribeFromRoleChanges(this.onRoleChange);
}, },
methods: { methods: {
getUserInfo() { async getUserInfo() {
this.openmct.user.getCurrentUser().then((user) => { const user = await this.openmct.user.getCurrentUser();
this.userName = user.getName(); this.userName = user.getName();
this.loggedIn = this.openmct.user.isLoggedIn(); this.role = this.openmct.user.getActiveRole();
this.loggedIn = this.openmct.user.isLoggedIn();
},
async fetchOrPromptForRole() {
const UserAPI = this.openmct.user;
const activeRole = UserAPI.getActiveRole();
this.role = activeRole;
if (!activeRole) {
this.promptForRoleSelection();
} else {
// only notify the user if they have more than one role available
const allRoles = await this.openmct.user.getPossibleRoles();
if (allRoles.length > 1) {
this.openmct.notifications.info(`You're logged in as role ${activeRole}`);
}
}
},
async promptForRoleSelection() {
const allRoles = await this.openmct.user.getPossibleRoles();
const selectionOptions = allRoles.map((role) => ({
key: role,
name: role
}));
// automatically select only role option
if (selectionOptions.length === 1) {
this.updateRole(selectionOptions[0].key);
return;
}
this.roleSelectionDialog = this.openmct.overlays.selection({
selectionOptions,
iconClass: 'alert',
title: 'Select Role',
message: 'Please select your role for operator status.',
currentSelection: this.role,
onChange: (event) => {
this.inputRoleSelection = event.target.value;
},
buttons: [
{
label: 'Select',
emphasis: true,
callback: () => {
this.roleSelectionDialog.dismiss();
this.roleSelectionDialog = undefined;
const inputValueOrDefault = this.inputRoleSelection || selectionOptions[0].key;
this.updateRole(inputValueOrDefault);
this.openmct.notifications.info(`Successfully set new role to ${this.role}`);
}
}
]
}); });
},
onRoleChange(event) {
const role = event.data;
this.roleSelectionDialog?.dismiss();
this.setRoleSelection(role);
},
setRoleSelection(role) {
this.role = role;
},
updateRole(role) {
this.setRoleSelection(role);
this.openmct.user.setActiveRole(role);
// update other tabs through broadcast channel
this.roleChannel.broadcastNewRole(role);
} }
} }
}; };

View File

@ -113,6 +113,10 @@ export default {
mounted() { mounted() {
this.openmct.notifications.on('notification', this.showNotification); this.openmct.notifications.on('notification', this.showNotification);
this.openmct.notifications.on('dismiss-all', this.clearModel); this.openmct.notifications.on('dismiss-all', this.clearModel);
if (this.openmct.notifications.activeNotification) {
activeNotification = this.openmct.notifications.activeNotification;
this.showNotification(activeNotification);
}
}, },
methods: { methods: {
showNotification(notification) { showNotification(notification) {

View File

@ -25,6 +25,7 @@ import MCT from 'MCT';
let nativeFunctions = []; let nativeFunctions = [];
let mockObjects = setMockObjects(); let mockObjects = setMockObjects();
const EXAMPLE_ROLE = 'flight';
const DEFAULT_TIME_OPTIONS = { const DEFAULT_TIME_OPTIONS = {
timeSystemKey: 'utc', timeSystemKey: 'utc',
bounds: { bounds: {
@ -38,6 +39,7 @@ export function createOpenMct(timeSystemOptions = DEFAULT_TIME_OPTIONS) {
openmct.install(openmct.plugins.LocalStorage()); openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.UTCTimeSystem()); openmct.install(openmct.plugins.UTCTimeSystem());
openmct.setAssetPath('/base'); openmct.setAssetPath('/base');
openmct.user.setActiveRole(EXAMPLE_ROLE);
const timeSystemKey = timeSystemOptions.timeSystemKey; const timeSystemKey = timeSystemOptions.timeSystemKey;
const start = timeSystemOptions.bounds.start; const start = timeSystemOptions.bounds.start;