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

Role selection for operator status roles #6706

Merged
merged 60 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
f5d5721
Add additional test roles to example user
michaelrogers May 8, 2023
6a9b961
Add session storage and role to user indicator
michaelrogers May 26, 2023
6764ff0
Update example user provider
michaelrogers May 26, 2023
bd56119
Added selection dialog to overlays and implemented for operator status
michaelrogers May 31, 2023
53fea64
Display role in user indicator
michaelrogers May 31, 2023
2c4b3c3
Updates to broadcast channel lifecycle
michaelrogers Jun 1, 2023
3b3be58
Update comment
michaelrogers Jun 1, 2023
01131c1
Comment width
michaelrogers Jun 1, 2023
035cb1e
UserAPI role updates and UserIndicator improvement
michaelrogers Jun 1, 2023
1f9cc05
Moved prompt to UserIndicator
michaelrogers Jun 7, 2023
2010191
Reconnect channel on error and UserIndicator updates
michaelrogers Jun 7, 2023
78d6866
Updates to status api canPRovideStatusForRole
michaelrogers Jun 14, 2023
550fa7e
Cleanup
michaelrogers Jun 14, 2023
c0e7804
Store status roles in an array instead of a singular value
michaelrogers Jun 14, 2023
ec7f954
Added success notification and cleanup
michaelrogers Jun 14, 2023
7261662
Lint
michaelrogers Jun 14, 2023
7a4d661
Removed unused role param from status api call
michaelrogers Jun 15, 2023
c52ee6c
Remove default status role from example user plugin
michaelrogers Jun 15, 2023
56827ad
Removed status.getStatusRoleForCurrentUser
michaelrogers Jun 16, 2023
7388b48
Cleanup
michaelrogers Jun 16, 2023
af1fc68
Cleanup
michaelrogers Jun 17, 2023
6444e91
Moved roleChannel to private field
michaelrogers Jun 20, 2023
528f87e
Separated input value from active role value
michaelrogers Jun 20, 2023
71d8ffd
More flight like status role names and parameter names
michaelrogers Jun 20, 2023
76faabf
Update statusRole parameter name
michaelrogers Jun 20, 2023
6c087a1
Update default selection for roles if input is not chosen
michaelrogers Jun 21, 2023
97ae44b
Update OperatorStatusIndicator install to hide if an observer
michaelrogers Jun 21, 2023
295efee
console.log
michaelrogers Jun 21, 2023
4902b31
Return null instead of undefined
michaelrogers Jun 21, 2023
e53e761
Remove unneccesary filter on allRoles
michaelrogers Jun 21, 2023
f51d47c
refactor: format with prettier
michaelrogers Jun 23, 2023
de07e03
Merge branch 'master' into mct6555
michaelrogers Jun 23, 2023
b0b92ee
Undid merge error
michaelrogers Jun 23, 2023
be5ba7c
Merge conflict extra line
michaelrogers Jun 23, 2023
aba026f
Copyright statement
michaelrogers Jul 6, 2023
06f7141
RoleChannelProvider to RoleChannel
michaelrogers Jul 6, 2023
5a6755c
Throw error on no provider
michaelrogers Jul 6, 2023
a7e10a9
Change RoleChannel to ActiveRoleSynchronizer and update method calls …
michaelrogers Jul 7, 2023
a148f10
Merge branch 'master' into mct6555
scottbell Jul 10, 2023
6c004d2
iconClass to alert
michaelrogers Jul 10, 2023
3dce8a4
Add role selection step to beforeEach
michaelrogers Jul 10, 2023
1d2c56e
example-role to flight
michaelrogers Jul 10, 2023
f7bc497
Dismiss overlay from exampleUser plugin which affected menu api posit…
michaelrogers Jul 11, 2023
bd1433d
Merge branch 'master' into mct6555
scottbell Jul 12, 2023
2854074
Make promptForRoleSelection async to allow for awaiting getPossibleRoles
khalidadil Jul 12, 2023
46202b4
Add log
khalidadil Jul 13, 2023
9ebe21e
Add check for activeRole
khalidadil Jul 13, 2023
81c33ef
Removed unneeded getStatusRoleForCurrentUser function
khalidadil Jul 13, 2023
9050b7a
Removed extractRoleFromEvent from ActiveRoleSynchronizer
michaelrogers Jul 13, 2023
a54bfc7
Set active role on openmct creation for testing
michaelrogers Jul 13, 2023
75475b4
Autoselect only role option
michaelrogers Jul 13, 2023
9e95ccb
await canProvideStatusForRole
michaelrogers Jul 13, 2023
eb15d43
Remove extract role from event
michaelrogers Jul 13, 2023
ea2168b
sessionStorage to localStorage
michaelrogers Jul 13, 2023
1806fca
Dismiss open overlay if other tab has selected a role
michaelrogers Jul 13, 2023
4199913
Display active notifications on mount and display active role in noti…
michaelrogers Jul 13, 2023
e6b8a26
Missing await on invocation
michaelrogers Jul 13, 2023
584377b
Prevent notification .off() error in unit tests
michaelrogers Jul 14, 2023
7984a21
Merge branch 'master' into mct6555
khalidadil Jul 14, 2023
a80b094
Merge branch 'master' into mct6555
akhenry Jul 14, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 22 additions & 12 deletions example/exampleUser/ExampleUserProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
10 changes: 6 additions & 4 deletions example/exampleUser/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
30 changes: 30 additions & 0 deletions src/api/overlays/OverlayAPI.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -130,6 +153,13 @@ class OverlayAPI {

return progressDialog;
}

selection(options) {
let selection = new Selection(options);
this.showOverlay(selection);

return selection;
}
}

export default OverlayAPI;
67 changes: 67 additions & 0 deletions src/api/overlays/Selection.js
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions src/api/overlays/components/SelectionComponent.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 37 additions & 0 deletions src/api/user/ActiveRoleSynchronizer.js
Original file line number Diff line number Diff line change
@@ -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;
39 changes: 14 additions & 25 deletions src/api/user/StatusAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -151,11 +151,16 @@ export default class StatusAPI extends EventEmitter {
* @param {Status} status The status to set for the provided role
* @returns {Promise<Boolean>} 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');
}
Expand Down Expand Up @@ -216,36 +221,20 @@ 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.
* @see StatusUserProvider
*/
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;
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/api/user/StatusUserProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
async getStatusRoleForCurrentUser() {}
}
Loading