diff --git a/e2e/tests/functional/plugins/operatorStatus/operatorStatus.e2e.spec.js b/e2e/tests/functional/plugins/operatorStatus/operatorStatus.e2e.spec.js
index b40dc2a67a..1fa5437bd8 100644
--- a/e2e/tests/functional/plugins/operatorStatus/operatorStatus.e2e.spec.js
+++ b/e2e/tests/functional/plugins/operatorStatus/operatorStatus.e2e.spec.js
@@ -47,6 +47,11 @@ test.describe('Operator Status', () => {
path: path.join(__dirname, '../../../../helper/', 'addInitOperatorStatus.js')
});
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
diff --git a/example/exampleUser/ExampleUserProvider.js b/example/exampleUser/ExampleUserProvider.js
index 683ddb1d4b..c92d780125 100644
--- a/example/exampleUser/ExampleUserProvider.js
+++ b/example/exampleUser/ExampleUserProvider.js
@@ -63,16 +63,24 @@ const STATUSES = [
* @implements {StatusUserProvider}
*/
export default class ExampleUserProvider extends EventEmitter {
- constructor(openmct, { defaultStatusRole } = { defaultStatusRole: undefined }) {
+ constructor(
+ openmct,
+ { statusRoles } = {
+ statusRoles: []
+ }
+ ) {
super();
this.openmct = openmct;
this.user = undefined;
this.loggedIn = false;
this.autoLoginUser = undefined;
- this.status = STATUSES[0];
+ this.statusRoleValues = statusRoles.map((role) => ({
+ role: role,
+ status: STATUSES[0]
+ }));
this.pollQuestion = undefined;
- this.defaultStatusRole = defaultStatusRole;
+ this.statusRoles = statusRoles;
this.ExampleUser = createExampleUser(this.openmct.user.User);
this.loginPromise = undefined;
@@ -94,14 +102,13 @@ export default class ExampleUserProvider extends EventEmitter {
return this.loginPromise;
}
- canProvideStatusForRole() {
- return Promise.resolve(true);
+ canProvideStatusForRole(role) {
+ return this.statusRoles.includes(role);
}
canSetPollQuestion() {
return Promise.resolve(true);
}
-
hasRole(roleId) {
if (!this.loggedIn) {
Promise.resolve(undefined);
@@ -110,16 +117,18 @@ export default class ExampleUserProvider extends EventEmitter {
return Promise.resolve(this.user.getRoles().includes(roleId));
}
- getStatusRoleForCurrentUser() {
- return Promise.resolve(this.defaultStatusRole);
+ getPossibleRoles() {
+ return this.user.getRoles();
}
getAllStatusRoles() {
- return Promise.resolve([this.defaultStatusRole]);
+ return Promise.resolve(this.statusRoles);
}
getStatusForRole(role) {
- return Promise.resolve(this.status);
+ const statusForRole = this.statusRoleValues.find((statusRole) => statusRole.role === role);
+
+ return Promise.resolve(statusForRole?.status);
}
async getDefaultStatusForRole(role) {
@@ -130,7 +139,8 @@ export default class ExampleUserProvider extends EventEmitter {
setStatusForRole(role, status) {
status.timestamp = Date.now();
- this.status = status;
+ const matchingIndex = this.statusRoleValues.findIndex((statusRole) => statusRole.role === role);
+ this.statusRoleValues[matchingIndex].status = status;
this.emit('statusChange', {
role,
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
// a normal authentication process
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;
return Promise.resolve();
diff --git a/example/exampleUser/plugin.js b/example/exampleUser/plugin.js
index 25c00b7dc0..ffbcae08ba 100644
--- a/example/exampleUser/plugin.js
+++ b/example/exampleUser/plugin.js
@@ -21,16 +21,18 @@
*****************************************************************************/
import ExampleUserProvider from './ExampleUserProvider';
+const AUTO_LOGIN_USER = 'mct-user';
+const STATUS_ROLES = ['flight', 'driver'];
export default function ExampleUserPlugin(
- { autoLoginUser, defaultStatusRole } = {
- autoLoginUser: 'guest',
- defaultStatusRole: 'test-role'
+ { autoLoginUser, statusRoles } = {
+ autoLoginUser: AUTO_LOGIN_USER,
+ statusRoles: STATUS_ROLES
}
) {
return function install(openmct) {
const userProvider = new ExampleUserProvider(openmct, {
- defaultStatusRole
+ statusRoles
});
if (autoLoginUser !== undefined) {
diff --git a/src/api/overlays/OverlayAPI.js b/src/api/overlays/OverlayAPI.js
index f7033f6b52..05811159bd 100644
--- a/src/api/overlays/OverlayAPI.js
+++ b/src/api/overlays/OverlayAPI.js
@@ -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 Dialog from './Dialog';
import ProgressDialog from './ProgressDialog';
+import Selection from './Selection';
/**
* The OverlayAPI is responsible for pre-pending templates to
@@ -130,6 +153,13 @@ class OverlayAPI {
return progressDialog;
}
+
+ selection(options) {
+ let selection = new Selection(options);
+ this.showOverlay(selection);
+
+ return selection;
+ }
}
export default OverlayAPI;
diff --git a/src/api/overlays/Selection.js b/src/api/overlays/Selection.js
new file mode 100644
index 0000000000..15c8365a69
--- /dev/null
+++ b/src/api/overlays/Selection.js
@@ -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: ''
+ }).$mount();
+
+ super({
+ element: component.$el,
+ size: 'fit',
+ dismissable: false,
+ onChange,
+ currentSelection,
+ ...options
+ });
+
+ this.once('destroy', () => {
+ component.$destroy();
+ });
+ }
+}
+
+export default Selection;
diff --git a/src/api/overlays/components/SelectionComponent.vue b/src/api/overlays/components/SelectionComponent.vue
new file mode 100644
index 0000000000..45ca72384c
--- /dev/null
+++ b/src/api/overlays/components/SelectionComponent.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+ {{ message }}
+
+
+
+
+
+
+
+
+
diff --git a/src/api/user/ActiveRoleSynchronizer.js b/src/api/user/ActiveRoleSynchronizer.js
new file mode 100644
index 0000000000..1ecbf28627
--- /dev/null
+++ b/src/api/user/ActiveRoleSynchronizer.js
@@ -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;
diff --git a/src/api/user/StatusAPI.js b/src/api/user/StatusAPI.js
index 159dcbb13b..30495e280d 100644
--- a/src/api/user/StatusAPI.js
+++ b/src/api/user/StatusAPI.js
@@ -140,9 +140,9 @@ export default class StatusAPI extends EventEmitter {
const provider = this.#userAPI.getProvider();
if (provider.canProvideStatusForRole) {
- return provider.canProvideStatusForRole(role);
+ return Promise.resolve(provider.canProvideStatusForRole(role));
} 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
* @returns {Promise} true if operation was successful, otherwise false.
*/
- setStatusForRole(role, status) {
+ setStatusForRole(status) {
const provider = this.#userAPI.getProvider();
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 {
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} 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} true if the configured UserProvider can provide status for the currently logged in user, false otherwise.
* @see StatusUserProvider
@@ -238,14 +228,13 @@ export default class StatusAPI extends EventEmitter {
async canProvideStatusForCurrentUser() {
const provider = this.#userAPI.getProvider();
- if (provider.getStatusRoleForCurrentUser) {
- const activeStatusRole = await this.#userAPI.getProvider().getStatusRoleForCurrentUser();
- const canProvideStatus = await this.canProvideStatusForRole(activeStatusRole);
-
- return canProvideStatus;
- } else {
+ if (!provider) {
return false;
}
+ const activeStatusRole = await this.#userAPI.getActiveRole();
+ const canProvideStatus = await this.canProvideStatusForRole(activeStatusRole);
+
+ return canProvideStatus;
}
/**
diff --git a/src/api/user/StatusUserProvider.js b/src/api/user/StatusUserProvider.js
index 85d132b52c..6389c36350 100644
--- a/src/api/user/StatusUserProvider.js
+++ b/src/api/user/StatusUserProvider.js
@@ -77,5 +77,4 @@ export default class StatusUserProvider extends UserProvider {
/**
* @returns {Promise} the active status role for the currently logged in user
*/
- async getStatusRoleForCurrentUser() {}
}
diff --git a/src/api/user/StoragePersistance.js b/src/api/user/StoragePersistance.js
new file mode 100644
index 0000000000..c9c53e6629
--- /dev/null
+++ b/src/api/user/StoragePersistance.js
@@ -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();
diff --git a/src/api/user/UserAPI.js b/src/api/user/UserAPI.js
index 98f4322ac7..5e588825c3 100644
--- a/src/api/user/UserAPI.js
+++ b/src/api/user/UserAPI.js
@@ -24,6 +24,7 @@ import EventEmitter from 'EventEmitter';
import { MULTIPLE_PROVIDER_ERROR, NO_PROVIDER_ERROR } from './constants';
import StatusAPI from './StatusAPI';
import User from './User';
+import StoragePersistance from './StoragePersistance';
class UserAPI extends EventEmitter {
/**
@@ -86,6 +87,58 @@ class UserAPI extends EventEmitter {
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
diff --git a/src/api/user/UserAPISpec.js b/src/api/user/UserAPISpec.js
index 0c5e0db947..8a849c9fa4 100644
--- a/src/api/user/UserAPISpec.js
+++ b/src/api/user/UserAPISpec.js
@@ -25,7 +25,7 @@ import { MULTIPLE_PROVIDER_ERROR } from './constants';
import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvider';
const USERNAME = 'Test User';
-const EXAMPLE_ROLE = 'example-role';
+const EXAMPLE_ROLE = 'flight';
describe('The User API', () => {
let openmct;
diff --git a/src/api/user/constants.js b/src/api/user/constants.js
index 94d1203629..c2e4ed02fb 100644
--- a/src/api/user/constants.js
+++ b/src/api/user/constants.js
@@ -22,3 +22,6 @@
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 ACTIVE_ROLE_LOCAL_STORAGE_KEY = 'ACTIVE_USER_ROLE';
+export const ACTIVE_ROLE_BROADCAST_CHANNEL_NAME = 'ActiveRoleChannel';
diff --git a/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue b/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue
index 26650005a1..19f754fbdf 100644
--- a/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue
+++ b/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue
@@ -48,7 +48,6 @@