diff --git a/.cspell.json b/.cspell.json index 1ec6c974d1..7769756a3d 100644 --- a/.cspell.json +++ b/.cspell.json @@ -496,6 +496,7 @@ "unnnormalized", "checksnapshots", "specced", + "composables", "countup" ], "dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US", "en-gb", "misc"], diff --git a/example/exampleUser/ExampleUserProvider.js b/example/exampleUser/ExampleUserProvider.js index 12c7fe2e5e..d018567e82 100644 --- a/example/exampleUser/ExampleUserProvider.js +++ b/example/exampleUser/ExampleUserProvider.js @@ -110,6 +110,11 @@ export default class ExampleUserProvider extends EventEmitter { canSetPollQuestion() { return Promise.resolve(true); } + + canSetMissionStatus() { + return Promise.resolve(false); + } + hasRole(roleId) { if (!this.loggedIn) { Promise.resolve(undefined); diff --git a/src/api/user/StatusAPI.js b/src/api/user/StatusAPI.js index ecbefeef6f..3f8476608a 100644 --- a/src/api/user/StatusAPI.js +++ b/src/api/user/StatusAPI.js @@ -32,6 +32,7 @@ export default class StatusAPI extends EventEmitter { this.onProviderStatusChange = this.onProviderStatusChange.bind(this); this.onProviderPollQuestionChange = this.onProviderPollQuestionChange.bind(this); + this.onMissionActionStatusChange = this.onMissionActionStatusChange.bind(this); this.listenToStatusEvents = this.listenToStatusEvents.bind(this); this.#openmct.once('destroy', () => { @@ -40,6 +41,7 @@ export default class StatusAPI extends EventEmitter { if (typeof provider?.off === 'function') { provider.off('statusChange', this.onProviderStatusChange); provider.off('pollQuestionChange', this.onProviderPollQuestionChange); + provider.off('missionActionStatusChange', this.onMissionActionStatusChange); } }); @@ -100,6 +102,67 @@ export default class StatusAPI extends EventEmitter { } } + /** + * Can the currently logged in user set the mission status. + * @returns {Promise} true if the currently logged in user can set the mission status, false otherwise. + */ + canSetMissionStatus() { + const provider = this.#userAPI.getProvider(); + + if (provider.canSetMissionStatus) { + return provider.canSetMissionStatus(); + } else { + return Promise.resolve(false); + } + } + + /** + * Fetch the current status for the given mission action + * @param {MissionAction} action + * @returns {string} + */ + getStatusForMissionAction(action) { + const provider = this.#userAPI.getProvider(); + + if (provider.getStatusForMissionAction) { + return provider.getStatusForMissionAction(action); + } else { + this.#userAPI.error('User provider does not support getting mission action status'); + } + } + + /** + * Fetch the list of possible mission status options (GO, NO-GO, etc.) + * @returns {Promise} the complete list of possible mission statuses + */ + async getPossibleMissionActionStatuses() { + const provider = this.#userAPI.getProvider(); + + if (provider.getPossibleMissionActionStatuses) { + const possibleOptions = await provider.getPossibleMissionActionStatuses(); + + return possibleOptions; + } else { + this.#userAPI.error('User provider does not support mission status options'); + } + } + + /** + * Fetch the list of possible mission actions + * @returns {Promise} the list of possible mission actions + */ + async getPossibleMissionActions() { + const provider = this.#userAPI.getProvider(); + + if (provider.getPossibleMissionActions) { + const possibleActions = await provider.getPossibleMissionActions(); + + return possibleActions; + } else { + this.#userAPI.error('User provider does not support mission statuses'); + } + } + /** * @returns {Promise>} the complete list of possible states that an operator can reply to a poll question with. */ @@ -166,6 +229,21 @@ export default class StatusAPI extends EventEmitter { } } + /** + * @param {MissionAction} action + * @param {MissionStatusOption} status + * @returns {Promise} true if operation was successful, otherwise false. + */ + setStatusForMissionAction(action, status) { + const provider = this.#userAPI.getProvider(); + + if (provider.setStatusForMissionAction) { + return provider.setStatusForMissionAction(action, status); + } else { + this.#userAPI.error('User provider does not support setting mission role status'); + } + } + /** * Resets the status of the provided role back to its default status. * @param {import("./UserAPI").Role} role The role to set the status for. @@ -245,6 +323,7 @@ export default class StatusAPI extends EventEmitter { if (typeof provider.on === 'function') { provider.on('statusChange', this.onProviderStatusChange); provider.on('pollQuestionChange', this.onProviderPollQuestionChange); + provider.on('missionActionStatusChange', this.onMissionActionStatusChange); } } @@ -261,14 +340,23 @@ export default class StatusAPI extends EventEmitter { onProviderPollQuestionChange(pollQuestion) { this.emit('pollQuestionChange', pollQuestion); } + + /** + * @private + */ + onMissionActionStatusChange({ action, status }) { + this.emit('missionActionStatusChange', { action, status }); + } } /** * @typedef {import('./UserProvider')} UserProvider */ + /** * @typedef {import('./StatusUserProvider')} StatusUserProvider */ + /** * The PollQuestion type * @typedef {Object} PollQuestion @@ -276,6 +364,19 @@ export default class StatusAPI extends EventEmitter { * @property {Number} timestamp - The time that the poll question was set. */ +/** + * The MissionStatus type + * @typedef {Object} MissionStatusOption + * @extends {Status} + * @property {String} color A color to be used when displaying the mission status + */ + +/** + * @typedef {Object} MissionAction + * @property {String} key A unique identifier for this action + * @property {String} label A human readable label for this action + */ + /** * The Status type * @typedef {Object} Status diff --git a/src/api/user/StatusUserProvider.js b/src/api/user/StatusUserProvider.js index 1cf35d9653..73a901a440 100644 --- a/src/api/user/StatusUserProvider.js +++ b/src/api/user/StatusUserProvider.js @@ -23,12 +23,12 @@ import UserProvider from './UserProvider.js'; export default class StatusUserProvider extends UserProvider { /** - * @param {('statusChange'|'pollQuestionChange')} event the name of the event to listen to + * @param {('statusChange'|'pollQuestionChange'|'missionActionStatusChange')} event the name of the event to listen to * @param {Function} callback a function to invoke when this event occurs */ on(event, callback) {} /** - * @param {('statusChange'|'pollQuestionChange')} event the name of the event to stop listen to + * @param {('statusChange'|'pollQuestionChange'|'missionActionStatusChange')} event the name of the event to stop listen to * @param {Function} callback the callback function used to register the listener */ off(event, callback) {} diff --git a/src/api/user/UserAPISpec.js b/src/api/user/UserAPISpec.js index aa231f3098..5af049dd3d 100644 --- a/src/api/user/UserAPISpec.js +++ b/src/api/user/UserAPISpec.js @@ -24,9 +24,6 @@ import ExampleUserProvider from '../../../example/exampleUser/ExampleUserProvide import { createOpenMct, resetApplicationState } from '../../utils/testing.js'; import { MULTIPLE_PROVIDER_ERROR } from './constants.js'; -const USERNAME = 'Test User'; -const EXAMPLE_ROLE = 'flight'; - describe('The User API', () => { let openmct; @@ -65,48 +62,4 @@ describe('The User API', () => { expect(openmct.user.hasProvider()).toBeTrue(); }); }); - - describe('provides the ability', () => { - let provider; - - beforeEach(() => { - provider = new ExampleUserProvider(openmct); - provider.autoLogin(USERNAME); - }); - - it('to check if a user (not specific) is logged in', (done) => { - expect(openmct.user.isLoggedIn()).toBeFalse(); - - openmct.user.on('providerAdded', () => { - expect(openmct.user.isLoggedIn()).toBeTrue(); - done(); - }); - - // this will trigger the user indicator plugin, - // which will in turn login the user - openmct.user.setProvider(provider); - }); - - it('to get the current user', (done) => { - openmct.user.setProvider(provider); - openmct.user - .getCurrentUser() - .then((apiUser) => { - expect(apiUser.name).toEqual(USERNAME); - }) - .finally(done); - }); - - it('to check if a user has a specific role (by id)', (done) => { - openmct.user.setProvider(provider); - let junkIdCheckPromise = openmct.user.hasRole('junk-id').then((hasRole) => { - expect(hasRole).toBeFalse(); - }); - let realIdCheckPromise = openmct.user.hasRole(EXAMPLE_ROLE).then((hasRole) => { - expect(hasRole).toBeTrue(); - }); - - Promise.all([junkIdCheckPromise, realIdCheckPromise]).finally(done); - }); - }); }); diff --git a/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue b/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue index 5a852f029b..ec2c6f9a3f 100644 --- a/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue +++ b/src/plugins/operatorStatus/operatorStatus/OperatorStatus.vue @@ -20,11 +20,7 @@ at runtime from the About dialog for additional information. -->