mirror of
https://github.com/nasa/openmct.git
synced 2025-05-02 16:53:24 +00:00
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:
parent
92329b3d8e
commit
32529ff6b2
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
67
src/api/overlays/Selection.js
Normal file
67
src/api/overlays/Selection.js
Normal 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;
|
34
src/api/overlays/components/SelectionComponent.vue
Normal file
34
src/api/overlays/components/SelectionComponent.vue
Normal 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>
|
37
src/api/user/ActiveRoleSynchronizer.js
Normal file
37
src/api/user/ActiveRoleSynchronizer.js
Normal 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;
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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() {}
|
|
||||||
}
|
}
|
||||||
|
37
src/api/user/StoragePersistance.js
Normal file
37
src/api/user/StoragePersistance.js
Normal 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();
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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';
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user