Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
[MM-28727] UX changes for Cloud onboarding flow (#6497)
Browse files Browse the repository at this point in the history
* Stashing before big re-work

* Need a full diff

* Stuffs working, cleaning up

* Fix bug

* Cleanup and linter + types passing

* More cleanup

* Fix lint

* Fix tests

* Merge master into MM-28727

* Add a transition for the LHS height change

* Set the state of show: true so the snapshot is still useful

* Fix style
  • Loading branch information
nickmisasi committed Sep 21, 2020
1 parent d2af6d6 commit 4992330
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 40 deletions.
8 changes: 6 additions & 2 deletions components/channel_view/channel_view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ export default class ChannelView extends React.PureComponent {
}).isRequired,
showTutorial: PropTypes.bool.isRequired,
showNextSteps: PropTypes.bool.isRequired,
showNextStepsTips: PropTypes.bool.isRequired,
isOnboardingHidden: PropTypes.bool.isRequired,
showNextStepsEphemeral: PropTypes.bool.isRequired,
channelIsArchived: PropTypes.bool.isRequired,
viewArchivedChannels: PropTypes.bool.isRequired,
actions: PropTypes.shape({
goToLastViewedChannel: PropTypes.func.isRequired,
setShowNextStepsView: PropTypes.func.isRequired,
getProfiles: PropTypes.func.isRequired,
}),
isCloud: PropTypes.bool.isRequired,
};
Expand Down Expand Up @@ -97,8 +100,9 @@ export default class ChannelView extends React.PureComponent {
this.props.actions.goToLastViewedChannel();
}

componentDidMount() {
if (this.props.showNextSteps) {
async componentDidMount() {
await this.props.actions.getProfiles();
if ((this.props.showNextSteps || this.props.showNextStepsTips) && !this.props.isOnboardingHidden) {
this.props.actions.setShowNextStepsView(true);
}
}
Expand Down
3 changes: 3 additions & 0 deletions components/channel_view/channel_view.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ describe('components/channel_view', () => {
},
showTutorial: false,
showNextSteps: false,
showNextStepsTips: false,
isOnboardingHidden: true,
showNextStepsEphemeral: false,
channelIsArchived: false,
viewArchivedChannels: false,
isCloud: false,
actions: {
goToLastViewedChannel: jest.fn(),
setShowNextStepsView: jest.fn(),
getProfiles: jest.fn(),
},
};

Expand Down
7 changes: 6 additions & 1 deletion components/channel_view/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {withRouter} from 'react-router-dom';

import {getProfiles} from 'mattermost-redux/actions/users';

import {getDirectTeammate} from 'utils/utils.jsx';
import {TutorialSteps, Preferences} from 'utils/constants';

import {goToLastViewedChannel} from 'actions/views/channel';
import {setShowNextStepsView} from 'actions/views/next_steps';
import {showNextSteps} from 'components/next_steps_view/steps';
import {isOnboardingHidden, showNextSteps, showNextStepsTips} from 'components/next_steps_view/steps';

import ChannelView from './channel_view.jsx';

Expand Down Expand Up @@ -60,6 +62,8 @@ function mapStateToProps(state) {
deactivatedChannel: channel ? getDeactivatedChannel(state, channel.id) : false,
showTutorial: enableTutorial && tutorialStep <= TutorialSteps.INTRO_SCREENS,
showNextSteps: showNextSteps(state),
showNextStepsTips: showNextStepsTips(state),
isOnboardingHidden: isOnboardingHidden(state),
showNextStepsEphemeral: state.views.nextSteps.show,
channelIsArchived: channel ? channel.delete_at !== 0 : false,
viewArchivedChannels,
Expand All @@ -72,6 +76,7 @@ function mapDispatchToProps(dispatch) {
actions: bindActionCreators({
setShowNextStepsView,
goToLastViewedChannel,
getProfiles,
}, dispatch),
};
}
Expand Down
20 changes: 12 additions & 8 deletions components/main_menu/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';

import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {
getMyTeams,
getJoinableTeamIds,
Expand All @@ -24,15 +24,18 @@ import {showMentions, showFlaggedPosts, closeRightHandSide, closeMenu as closeRh
import {openModal} from 'actions/views/modals';
import {getRhsState} from 'selectors/rhs';

import {nextStepsNotFinished} from 'components/next_steps_view/steps';
import {
showOnboarding,
showNextStepsTips,
showNextSteps,
} from 'components/next_steps_view/steps';

import MainMenu from './main_menu.jsx';

function mapStateToProps(state) {
const config = getConfig(state);
const currentTeam = getCurrentTeam(state);
const currentUser = getCurrentUser(state);
const license = getLicense(state);

const appDownloadLink = config.AppDownloadLink;
const enableCommands = config.EnableCommands === 'true';
Expand Down Expand Up @@ -92,13 +95,14 @@ function mapStateToProps(state) {
currentUser,
isMentionSearch: rhsState === RHSStates.MENTION,
teamIsGroupConstrained: Boolean(currentTeam.group_constrained),
isLicensedForLDAPGroups: state.entities.general.license.LDAPGroups === 'true',
isLicensedForLDAPGroups:
state.entities.general.license.LDAPGroups === 'true',
userLimit: getConfig(state).ExperimentalCloudUserLimit,
currentUsers: state.entities.admin.analytics.TOTAL_USERS,
userIsAdmin: isAdmin(
getMyTeamMember(state, currentTeam.id).roles,
),
showGettingStarted: !state.views.nextSteps.show && nextStepsNotFinished(state) && license.Cloud === 'true',
userIsAdmin: isAdmin(getMyTeamMember(state, currentTeam.id).roles),
showGettingStarted: showOnboarding(state),
showNextStepsTips: showNextStepsTips(state),
showNextSteps: showNextSteps(state),
};
}

Expand Down
3 changes: 2 additions & 1 deletion components/main_menu/main_menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class MainMenu extends React.PureComponent {
userIsAdmin: PropTypes.bool,
showGettingStarted: PropTypes.bool.isRequired,
intl: intlShape.isRequired,
showNextStepsTips: PropTypes.bool,
actions: PropTypes.shape({
openModal: PropTypes.func.isRequred,
showMentions: PropTypes.func,
Expand Down Expand Up @@ -371,7 +372,7 @@ class MainMenu extends React.PureComponent {
id='gettingStarted'
show={this.props.showGettingStarted}
onClick={() => this.props.actions.unhideNextSteps()}
text={formatMessage({id: 'navbar_dropdown.gettingStarted', defaultMessage: 'Getting Started'})}
text={formatMessage({id: this.props.showNextStepsTips ? 'sidebar_next_steps.tipsAndNextSteps' : 'navbar_dropdown.gettingStarted', defaultMessage: this.props.showNextStepsTips ? 'Tips & Next Steps' : 'Getting Started'})}
/>
<Menu.ItemAction
id='keyboardShortcuts'
Expand Down
1 change: 1 addition & 0 deletions components/next_steps_view/next_steps_view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('components/next_steps_view', () => {
const wrapper = shallow(
<NextStepsView {...baseProps}/>,
);
wrapper.setState({show: true});

expect(wrapper).toMatchSnapshot();
});
Expand Down
59 changes: 39 additions & 20 deletions components/next_steps_view/next_steps_view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {pageVisited, trackEvent} from 'actions/diagnostics_actions';
import Accordion from 'components/accordion';
import Card from 'components/card/card';
import {getAnalyticsCategory} from 'components/next_steps_view/step_helpers';
import {Preferences} from 'utils/constants';
import {Preferences, RecommendedNextSteps} from 'utils/constants';

import loadingIcon from 'images/spinner-48x48-blue.apng';

Expand Down Expand Up @@ -43,31 +43,37 @@ type State = {
showFinalScreen: boolean;
showTransitionScreen: boolean;
animating: boolean;
show: boolean;
}

export default class NextStepsView extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);

this.state = {
showFinalScreen: false,
showTransitionScreen: false,
animating: false,
show: false,
};
}

async componentDidMount() {
await this.props.actions.getProfiles();
pageVisited(getAnalyticsCategory(this.props.isFirstAdmin), 'pageview_welcome');

// If all steps are complete, don't render this and skip to the tips screen
if (this.getIncompleteStep() === null) {
// If all steps are complete, or user has skipped, don't render this and skip to the tips screen
if (this.getIncompleteStep() === null || this.checkStepsSkipped()) {
this.showFinalScreenNoAnimation();
}

// eslint-disable-next-line react/no-did-mount-set-state
this.setState({show: true});
pageVisited(getAnalyticsCategory(this.props.isFirstAdmin), 'pageview_welcome');
this.props.actions.closeRightHandSide();
}

checkStepsSkipped = () => {
return this.props.preferences.some((pref) => pref.name === RecommendedNextSteps.SKIP && pref.value === 'true');
}

getStartingStep = () => {
for (let i = 0; i < this.props.steps.length; i++) {
if (!this.isStepComplete(this.props.steps[i].id)) {
Expand Down Expand Up @@ -102,6 +108,16 @@ export default class NextStepsView extends React.PureComponent<Props, State> {
};
}

onSkipAll = async () => {
this.showFinalScreen();
this.props.actions.savePreferences(this.props.currentUser.id, [{
user_id: this.props.currentUser.id,
category: Preferences.RECOMMENDED_NEXT_STEPS,
name: RecommendedNextSteps.SKIP,
value: 'true',
}]);
}

onFinish = (setExpanded: (expandedKey: string) => void) => {
return async (id: string) => {
const stepIndex = this.getStepNumberFromId(id);
Expand All @@ -120,7 +136,7 @@ export default class NextStepsView extends React.PureComponent<Props, State> {

showFinalScreenNoAnimation = () => {
pageVisited(getAnalyticsCategory(this.props.isFirstAdmin), 'pageview_tips_next_steps');
this.setState({showFinalScreen: true});
this.setState({showFinalScreen: true, animating: false});
}

showFinalScreen = () => {
Expand Down Expand Up @@ -287,7 +303,7 @@ export default class NextStepsView extends React.PureComponent<Props, State> {
<div className='NextStepsView__skipGettingStarted'>
<button
className='NextStepsView__button tertiary'
onClick={this.showFinalScreen}
onClick={this.onSkipAll}
>
<FormattedMessage
id='next_steps_view.skipGettingStarted'
Expand All @@ -310,18 +326,21 @@ export default class NextStepsView extends React.PureComponent<Props, State> {
id='app-content'
className='app__content NextStepsView'
>
<OnboardingBgSvg/>
{this.renderMainBody()}
{this.renderTransitionScreen()}
<NextStepsTips
showFinalScreen={this.state.showFinalScreen}
animating={this.state.animating}
stopAnimating={this.stopAnimating}
isFirstAdmin={this.props.isFirstAdmin}
savePreferences={this.props.actions.savePreferences}
currentUserId={this.props.currentUser.id}
setShowNextStepsView={this.props.actions.setShowNextStepsView}
/>
{this.state.show &&
<>
<OnboardingBgSvg/>
{this.renderMainBody()}
{this.renderTransitionScreen()}
<NextStepsTips
showFinalScreen={this.state.showFinalScreen}
animating={this.state.animating}
stopAnimating={this.stopAnimating}
isFirstAdmin={this.props.isFirstAdmin}
savePreferences={this.props.actions.savePreferences}
currentUserId={this.props.currentUser.id}
setShowNextStepsView={this.props.actions.setShowNextStepsView}
/>
</>}
</section>
);
}
Expand Down
7 changes: 4 additions & 3 deletions components/next_steps_view/steps.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {showNextSteps, getSteps} from './steps';
import {showNextSteps, getSteps, isOnboardingHidden, nextStepsNotFinished} from './steps';

//
describe('components/next_steps_view/steps', () => {
test('should not show next steps if not cloud', () => {
const goodState = {
Expand Down Expand Up @@ -64,7 +65,7 @@ describe('components/next_steps_view/steps', () => {
},
};

expect(showNextSteps(state as any)).toBe(false);
expect(isOnboardingHidden(state as any)).toBe(true);
});

test('should not show the view if all steps are complete', () => {
Expand Down Expand Up @@ -100,7 +101,7 @@ describe('components/next_steps_view/steps', () => {
},
};

expect(showNextSteps(state as any)).toBe(false);
expect(nextStepsNotFinished(state as any)).toBe(true);
});

test('should only show admin steps for admin users', () => {
Expand Down
50 changes: 47 additions & 3 deletions components/next_steps_view/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export const isFirstAdmin = createSelector(
(state: GlobalState) => getCurrentUser(state),
(state: GlobalState) => getUsers(state),
(currentUser, users) => {
if (!currentUser.roles.includes('system_admin')) {
return false;
}
const userIds = Object.keys(users);
for (const userId of userIds) {
const user = users[userId];
Expand Down Expand Up @@ -118,12 +121,29 @@ export const getSteps = createSelector(
);

const getCategory = makeGetCategory();
export const showOnboarding = createSelector(
(state: GlobalState) => showNextSteps(state),
(state: GlobalState) => showNextStepsTips(state),
(state: GlobalState) => getLicense(state),
(state: GlobalState) => state.views.nextSteps.show,
(showNextSteps, showNextStepsTips, license, showNextStepsEphemeral) => {
return !showNextStepsEphemeral && license.Cloud === 'true' && (showNextSteps || showNextStepsTips);
});

export const isOnboardingHidden = createSelector(
(state: GlobalState) => getCategory(state, Preferences.RECOMMENDED_NEXT_STEPS),
(stepPreferences) => {
return stepPreferences.some((pref) => (pref.name === RecommendedNextSteps.HIDE && pref.value === 'true'));
},
);

// Only show next steps if they haven't been skipped and there are steps unfinished
export const showNextSteps = createSelector(
(state: GlobalState) => getCategory(state, Preferences.RECOMMENDED_NEXT_STEPS),
(state: GlobalState) => getLicense(state),
(state: GlobalState) => nextStepsNotFinished(state),
(stepPreferences, license, nextStepsNotFinished) => {
if (stepPreferences.some((pref) => pref.name === RecommendedNextSteps.HIDE && pref.value === 'true')) {
if (stepPreferences.some((pref) => (pref.name === RecommendedNextSteps.SKIP && pref.value === 'true'))) {
return false;
}

Expand All @@ -135,11 +155,35 @@ export const showNextSteps = createSelector(
},
);

// Only show tips if they have been skipped, or there are no unfinished steps
export const showNextStepsTips = createSelector(
(state: GlobalState) => getCategory(state, Preferences.RECOMMENDED_NEXT_STEPS),
(state: GlobalState) => getLicense(state),
(state: GlobalState) => nextStepsNotFinished(state),
(stepPreferences, license, nextStepsNotFinished) => {
if (stepPreferences.some((pref) => (pref.name === RecommendedNextSteps.SKIP && pref.value === 'true'))) {
return true;
}

if (license.Cloud !== 'true') {
return false;
}

return !nextStepsNotFinished;
},
);

// Loop through all Steps. For each step, check that
export const nextStepsNotFinished = createSelector(
(state: GlobalState) => getCategory(state, Preferences.RECOMMENDED_NEXT_STEPS),
(state: GlobalState) => getCurrentUser(state),
(stepPreferences, currentUser) => {
const checkPref = (step: StepType) => stepPreferences.some((pref) => (pref.name === step.id && pref.value === 'true') || !isStepForUser(step, currentUser.roles));
(state: GlobalState) => isFirstAdmin(state),
(stepPreferences, currentUser, firstAdmin) => {
let roles = currentUser.roles;
if (!firstAdmin) {
roles = 'system_user';
}
const checkPref = (step: StepType) => stepPreferences.some((pref) => (pref.name === step.id && pref.value === 'true') || !isStepForUser(step, roles));
return !Steps.every(checkPref);
},
);
3 changes: 2 additions & 1 deletion components/sidebar/sidebar_next_steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {getSteps} from '../../next_steps_view/steps';

import {openModal, closeModal} from 'actions/views/modals';
import {setShowNextStepsView} from 'actions/views/next_steps';
import {showNextSteps} from 'components/next_steps_view/steps';
import {showNextSteps, showNextStepsTips} from 'components/next_steps_view/steps';
import {GlobalState} from 'types/store';
import {Preferences} from 'utils/constants';

Expand All @@ -25,6 +25,7 @@ function makeMapStateToProps() {
active: state.views.nextSteps.show,
steps: getSteps(state),
showNextSteps: showNextSteps(state),
showNextStepsTips: showNextStepsTips(state),
currentUser: getCurrentUser(state),
currentUserId: getCurrentUserId(state),
preferences: getCategory(state, Preferences.RECOMMENDED_NEXT_STEPS),
Expand Down
Loading

0 comments on commit 4992330

Please sign in to comment.