Files
vault/ui/app/services/wizard.js
hashicorp-copywrite[bot] 0b12cdcfd1 [COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

362 lines
11 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { next } from '@ember/runloop';
import { typeOf } from '@ember/utils';
import Service, { inject as service } from '@ember/service';
import { Machine } from 'xstate';
import { capitalize } from '@ember/string';
import getStorage from 'vault/lib/token-storage';
import { STORAGE_KEYS, DEFAULTS, MACHINES } from 'vault/helpers/wizard-constants';
const {
TUTORIAL_STATE,
COMPONENT_STATE,
FEATURE_STATE,
FEATURE_LIST,
FEATURE_STATE_HISTORY,
COMPLETED_FEATURES,
RESUME_URL,
RESUME_ROUTE,
} = STORAGE_KEYS;
const TutorialMachine = Machine(MACHINES.tutorial);
let FeatureMachine = null;
export default Service.extend(DEFAULTS, {
router: service(),
showWhenUnauthenticated: false,
featureMachineHistory: null,
init() {
this._super(...arguments);
this.initializeMachines();
},
initializeMachines() {
if (!this.storageHasKey(TUTORIAL_STATE)) {
const state = TutorialMachine.initialState;
this.saveState('currentState', state.value);
this.saveExtState(TUTORIAL_STATE, state.value);
}
this.saveState('currentState', this.getExtState(TUTORIAL_STATE));
if (this.storageHasKey(COMPONENT_STATE)) {
this.set('componentState', this.getExtState(COMPONENT_STATE));
}
const stateNodes = TutorialMachine.getStateNodes(this.currentState);
this.executeActions(
stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []),
null,
'tutorial'
);
if (this.storageHasKey(FEATURE_LIST)) {
this.set('featureList', this.getExtState(FEATURE_LIST));
if (this.storageHasKey(FEATURE_STATE_HISTORY)) {
this.set('featureMachineHistory', this.getExtState(FEATURE_STATE_HISTORY));
}
this.saveState(
'featureState',
this.getExtState(FEATURE_STATE) || (FeatureMachine ? FeatureMachine.initialState : null)
);
this.saveExtState(FEATURE_STATE, this.featureState);
this.buildFeatureMachine();
}
},
clearFeatureData() {
const storage = this.storage();
// empty storage
[FEATURE_LIST, FEATURE_STATE, FEATURE_STATE_HISTORY, COMPLETED_FEATURES].forEach((key) =>
storage.removeItem(key)
);
this.set('currentMachine', null);
this.set('featureMachineHistory', null);
this.set('featureState', null);
this.set('featureList', null);
},
restartGuide() {
this.clearFeatureData();
const storage = this.storage();
// empty storage
[TUTORIAL_STATE, COMPONENT_STATE, RESUME_URL, RESUME_ROUTE].forEach((key) => storage.removeItem(key));
// reset wizard state
this.setProperties(DEFAULTS);
// restart machines from blank state
this.initializeMachines();
// progress machine to 'active.select'
this.transitionTutorialMachine('idle', 'AUTH');
},
saveFeatureHistory(state) {
if (
this.getCompletedFeatures().length === 0 &&
this.featureMachineHistory === null &&
(state === 'idle' || state === 'wrap')
) {
const newHistory = [state];
this.set('featureMachineHistory', newHistory);
} else {
if (this.featureMachineHistory) {
if (!this.featureMachineHistory.includes(state)) {
const newHistory = this.featureMachineHistory.addObject(state);
this.set('featureMachineHistory', newHistory);
} else {
//we're repeating steps
const stepIndex = this.featureMachineHistory.indexOf(state);
const newHistory = this.featureMachineHistory.splice(0, stepIndex + 1);
this.set('featureMachineHistory', newHistory);
}
}
}
if (this.featureMachineHistory) {
this.saveExtState(FEATURE_STATE_HISTORY, this.featureMachineHistory);
}
},
saveState(stateType, state) {
if (state.value) {
state = state.value;
}
let stateKey = '';
while (typeOf(state) === 'object') {
const newState = Object.keys(state);
stateKey += newState + '.';
state = state[newState];
}
stateKey += state;
this.set(stateType, stateKey);
if (stateType === 'featureState') {
//only track progress if we are on the first step of the first feature
this.saveFeatureHistory(state);
}
},
transitionTutorialMachine(currentState, event, extendedState) {
if (extendedState) {
this.set('componentState', extendedState);
this.saveExtState(COMPONENT_STATE, extendedState);
}
const { actions, value } = TutorialMachine.transition(currentState, event);
this.saveState('currentState', value);
this.saveExtState(TUTORIAL_STATE, this.currentState);
this.executeActions(actions, event, 'tutorial');
},
transitionFeatureMachine(currentState, event, extendedState) {
if (!FeatureMachine || !this.currentState.includes('active')) {
return;
}
if (extendedState) {
this.set('componentState', extendedState);
this.saveExtState(COMPONENT_STATE, extendedState);
}
const { actions, value } = FeatureMachine.transition(currentState, event, this.componentState);
this.saveState('featureState', value);
this.saveExtState(FEATURE_STATE, value);
this.executeActions(actions, event, 'feature');
// if all features were completed, the FeatureMachine gets nulled
// out and won't exist here as there is no next step
if (FeatureMachine) {
let next;
if (this.currentMachine === 'secrets' && value === 'display') {
next = FeatureMachine.transition(value, 'REPEAT', this.componentState);
} else {
next = FeatureMachine.transition(value, 'CONTINUE', this.componentState);
}
this.saveState('nextStep', next.value);
}
},
saveExtState(key, value) {
this.storage().setItem(key, value);
},
getExtState(key) {
return this.storage().getItem(key);
},
storageHasKey(key) {
return Boolean(this.getExtState(key));
},
executeActions(actions, event, machineType) {
let transitionURL;
let expectedRouteName;
const router = this.router;
for (const action of actions) {
let type = action;
if (action.type) {
type = action.type;
}
switch (type) {
case 'render':
this.set(`${action.level}Component`, action.component);
break;
case 'routeTransition':
expectedRouteName = action.params[0];
transitionURL = router.urlFor(...action.params).replace(/^\/ui/, '');
next(() => {
router.transitionTo(...action.params);
});
break;
case 'saveFeatures':
this.saveFeatures(event.features);
break;
case 'completeFeature':
this.completeFeature();
break;
case 'handleDismissed':
this.handleDismissed();
break;
case 'handlePaused':
this.handlePaused();
return;
case 'handleResume':
this.handleResume();
break;
case 'showTutorialWhenAuthenticated':
this.set('showWhenUnauthenticated', false);
break;
case 'showTutorialAlways':
this.set('showWhenUnauthenticated', true);
break;
case 'clearFeatureData':
this.clearFeatureData();
break;
case 'continueFeature':
this.transitionFeatureMachine(this.featureState, 'CONTINUE', this.componentState);
break;
default:
break;
}
}
if (machineType === 'tutorial') {
return;
}
// if we're transitioning in the actions, we want that url,
// else we want the URL we land on in didTransition in the
// application route - we'll notify the application route to
// update the route
if (transitionURL) {
this.set('expectedURL', transitionURL);
this.set('expectedRouteName', expectedRouteName);
this.set('setURLAfterTransition', false);
} else {
this.set('setURLAfterTransition', true);
}
},
handlePaused() {
const expected = this.expectedURL;
if (expected) {
this.saveExtState(RESUME_URL, this.expectedURL);
this.saveExtState(RESUME_ROUTE, this.expectedRouteName);
}
},
handleResume() {
const resumeURL = this.storage().getItem(RESUME_URL);
if (!resumeURL) {
return;
}
this.router
.transitionTo(resumeURL)
.followRedirects()
.then(() => {
this.set('expectedRouteName', this.storage().getItem(RESUME_ROUTE));
this.set('expectedURL', resumeURL);
this.initializeMachines();
this.storage().removeItem(RESUME_URL);
});
},
handleDismissed() {
this.storage().removeItem(FEATURE_STATE);
this.storage().removeItem(FEATURE_LIST);
this.storage().removeItem(FEATURE_STATE_HISTORY);
this.storage().removeItem(COMPONENT_STATE);
},
saveFeatures(features) {
this.set('featureList', features);
this.saveExtState(FEATURE_LIST, this.featureList);
this.buildFeatureMachine();
},
buildFeatureMachine() {
if (this.featureList === null) {
return;
}
this.startFeature();
const nextFeature = this.featureList.length > 1 ? capitalize(this.featureList.objectAt(1)) : 'Finish';
this.set('nextFeature', nextFeature);
let next;
if (this.currentMachine === 'secrets' && this.featureState === 'display') {
next = FeatureMachine.transition(this.featureState, 'REPEAT', this.componentState);
} else {
next = FeatureMachine.transition(this.featureState, 'CONTINUE', this.componentState);
}
this.saveState('nextStep', next.value);
const stateNodes = FeatureMachine.getStateNodes(this.featureState);
this.executeActions(
stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []),
null,
'feature'
);
},
startFeature() {
const FeatureMachineConfig = MACHINES[this.featureList.objectAt(0)];
FeatureMachine = Machine(FeatureMachineConfig);
this.set('currentMachine', this.featureList.objectAt(0));
if (this.storageHasKey(FEATURE_STATE)) {
this.saveState('featureState', this.getExtState(FEATURE_STATE));
} else {
this.saveState('featureState', FeatureMachine.initialState);
}
this.saveExtState(FEATURE_STATE, this.featureState);
},
getCompletedFeatures() {
if (this.storageHasKey(COMPLETED_FEATURES)) {
return this.getExtState(COMPLETED_FEATURES).toArray();
}
return [];
},
completeFeature() {
const features = this.featureList;
const done = features.shift();
if (!this.getExtState(COMPLETED_FEATURES)) {
const completed = [];
completed.push(done);
this.saveExtState(COMPLETED_FEATURES, completed);
} else {
this.saveExtState(COMPLETED_FEATURES, this.getExtState(COMPLETED_FEATURES).toArray().addObject(done));
}
this.saveExtState(FEATURE_LIST, features.length ? features : null);
this.storage().removeItem(FEATURE_STATE);
if (this.featureMachineHistory) {
this.set('featureMachineHistory', []);
this.saveExtState(FEATURE_STATE_HISTORY, []);
}
if (features.length > 0) {
this.buildFeatureMachine();
} else {
this.storage().removeItem(FEATURE_LIST);
FeatureMachine = null;
this.transitionTutorialMachine(this.currentState, 'DONE');
}
},
storage() {
return getStorage();
},
});