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

Move and modularise guided tour state #43723

Merged
merged 6 commits into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/blocks/inline-help/inline-help-rich-result.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Button } from '@automattic/components';
import { decodeEntities, preventWidows } from 'lib/formatting';
import { recordTracksEvent } from 'state/analytics/actions';
import getSearchQuery from 'state/inline-help/selectors/get-search-query';
import { requestGuidedTour } from 'state/ui/guided-tours/actions';
import { requestGuidedTour } from 'state/guided-tours/actions';
import { openSupportArticleDialog } from 'state/inline-support-article/actions';

const amendYouTubeLink = ( link = '' ) =>
Expand Down
4 changes: 2 additions & 2 deletions client/layout/guided-tours/component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import { recordTracksEvent } from 'lib/analytics/tracks';
import AllTours from './all-tours';
import QueryPreferences from 'components/data/query-preferences';
import { RootChild } from '@automattic/components';
import { getGuidedTourState } from 'state/ui/guided-tours/selectors';
import { getGuidedTourState } from 'state/guided-tours/selectors';
import { getLastAction } from 'state/ui/action-log/selectors';
import { getSectionName, isSectionLoading } from 'state/ui/selectors';
import getInitialQueryArguments from 'state/selectors/get-initial-query-arguments';
import {
nextGuidedTourStep,
quitGuidedTour,
resetGuidedToursHistory,
} from 'state/ui/guided-tours/actions';
} from 'state/guided-tours/actions';

/**
* Style dependencies
Expand Down
4 changes: 2 additions & 2 deletions client/layout/guided-tours/docs/ARCHITECTURE-FUTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ As of August 2017, Guided Tours impacts Calypso's main bundle `build` as follows

- `layout/guided-tours`, which includes the view layer and its helpers, as well as individual tour configs: 76 KB stat size, 43 KB parsed, 9 KB gzipped
- `state/ui/action-log`, for which `yarn run analyze-bundles` doesn't provided fine-grained data, but whose raw source, excluding `test`, clocks at 2.5 KB or 1 KB gzipped
- `state/ui/guided-tours`, for which `yarn run analyze-bundles` doesn't provided fine-grained data, but whose raw source, excluding `test`, clocks at 15 KB or 4 KB gzipped
- `state/guided-tours`, for which `yarn run analyze-bundles` doesn't provided fine-grained data, but whose raw source, excluding `test`, clocks at 15 KB or 4 KB gzipped

We don't yet support lazy loading of branches of state, and ultimately `state/ui/action-log` has to be gathering information and `state/ui/guided-tours` has to be running selectors to determine if there is a tour to trigger — luckily, it is also the smaller piece.
We don't yet support lazy loading of branches of state, and ultimately `state/ui/action-log` has to be gathering information and `state/guided-tours` has to be running selectors to determine if there is a tour to trigger — luckily, it is also the smaller piece.

However, the rest can be lazy-loaded.

Expand Down
2 changes: 1 addition & 1 deletion client/layout/guided-tours/docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ With `actionLog`, any number of selectors can be written and composed to process

All of these different ways to leverage the action log constitute *triggers**. Presently, we have one family of triggers: triggers based on navigation to a specific path. This is what is at play when tour authors define a tour with `<Tour path="/themes">`, for instance.

The last high-level piece to understand is that Guided Tours barely has any explicit state (e.g. "which step of a tour are we in?", "how long is the tour taking to complete?"), instead relying as much as possible on the action log, which includes a dedicated `GUIDED_TOUR_UPDATE` action to signal tour transitions. The result is that, instead of a hypothetical `state.ui.guidedTours.isTourRunning && <Tour />`, we find a cascade of selectors that ultimately compute the state of Guided Tours. This is achievable thanks to a lot of memoization with `createSelector`:
The last high-level piece to understand is that Guided Tours barely has any explicit state (e.g. "which step of a tour are we in?", "how long is the tour taking to complete?"), instead relying as much as possible on the action log, which includes a dedicated `GUIDED_TOUR_UPDATE` action to signal tour transitions. The result is that, instead of a hypothetical `state.guidedTours.isTourRunning && <Tour />`, we find a cascade of selectors that ultimately compute the state of Guided Tours. This is achievable thanks to a lot of memoization with `createSelector`:

```text
getGuidedTourState
Expand Down
4 changes: 2 additions & 2 deletions client/layout/guided-tours/docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import {
isNewUser,
isEnabled,
isSelectedSitePreviewable,
} from 'state/ui/guided-tours/contexts';
} from 'state/guided-tours/contexts';

export const TutorialSitePreviewTour = makeTour(
);
Expand Down Expand Up @@ -181,7 +181,7 @@ Note that we've set the `enabled` variation to 0% so we don't show the tour to a

Now we need to make sure the tour only triggers if the user in the `enabled` variant.

First, add an import for `isAbTestInVariant` to the list of things we import from `state/ui/guided-tours/contexts` in `meta.js`.
First, add an import for `isAbTestInVariant` to the list of things we import from `state/guided-tours/contexts` in `meta.js`.

Now, use the import in the `when` property like so:

Expand Down
10 changes: 5 additions & 5 deletions client/layout/guided-tours/docs/examples/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Guided Tours Examples

This directory contains examples for the Guided Tours framework -- both tours and selectors. Use them as inspiration and starting points for your own tours.
This directory contains examples for the Guided Tours framework -- both tours and selectors. Use them as inspiration and starting points for your own tours.

## Tours

To try one of the example tours, include it in `client/layout/guided-tours/config.js`. If you want to base your own tour off of an example, copy it to the `../../tours` directory.
To try one of the example tours, include it in `client/layout/guided-tours/config.js`. If you want to base your own tour off of an example, copy it to the `../../tours` directory.

When we archive an unused tour as an example, we aim to move all the selectors only used by that tour into the tour itself so they don't affect the build process any longer. When you introduce a tour that uses one of those selectors, remember to move the selector back to where it belongs (e.g. `state/ui/guided-tours/contexts`).
When we archive an unused tour as an example, we aim to move all the selectors only used by that tour into the tour itself so they don't affect the build process any longer. When you introduce a tour that uses one of those selectors, remember to move the selector back to where it belongs (e.g. `state/guided-tours/contexts`).

- `simple-payments-end-of-year-guide.js`: This tour was used as a temporary end-of-year advertisement for the Simple Payments feature. It triggers on a variety of paths but only for premium and business users. The tour guides people through the creation of a page and the insertion of a Simple Payments button.
- `simple-payments-end-of-year-guide.js`: This tour was used as a temporary end-of-year advertisement for the Simple Payments feature. It triggers on a variety of paths but only for premium and business users. The tour guides people through the creation of a page and the insertion of a Simple Payments button.

## Selectors etc.

- `hasSelectedSitePremiumOrBusinessPlan` (in `simple-payments-end-of-year-guide.js`): true if the selected site is on the Premium or Business plan.
- `hasSelectedSitePremiumOrBusinessPlan` (in `simple-payments-end-of-year-guide.js`): true if the selected site is on the Premium or Business plan.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { getSitePlan } from 'state/sites/selectors';
import { getSelectedSiteId } from 'state/ui/selectors';

// NOTE: selector moved here because tour is no longer active and serves as example only
// to use in a tour, move back to 'state/ui/guided-tours/contexts' (see commented out import above)
// to use in a tour, move back to 'state/guided-tours/contexts' (see commented out import above)
/**
* Returns true if the selected site is on the Premium or Business plan
*
Expand Down
3 changes: 1 addition & 2 deletions client/layout/guided-tours/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
/**
* External dependencies
*/

import React from 'react';
import { connect } from 'react-redux';

/**
* Internal dependencies
*/
import AsyncLoad from 'components/async-load';
import { getGuidedTourState } from 'state/ui/guided-tours/selectors';
import { getGuidedTourState } from 'state/guided-tours/selectors';

function GuidedTours( { shouldShow } ) {
if ( ! shouldShow ) {
Expand Down
2 changes: 1 addition & 1 deletion client/layout/guided-tours/tours/activity-log-tour/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isDesktop } from '@automattic/viewport';
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { isNotNewUser } from 'state/ui/guided-tours/contexts';
import { isNotNewUser } from 'state/guided-tours/contexts';

export default {
name: 'activityLogTour',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isDesktop } from '@automattic/viewport';
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { isNewUser } from 'state/ui/guided-tours/contexts';
import { isNewUser } from 'state/guided-tours/contexts';
import { isCurrentUserEmailVerified } from 'state/current-user/selectors';

export default {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { hasUserPastedFromGoogleDocs } from 'state/ui/guided-tours/contexts';
import { hasUserPastedFromGoogleDocs } from 'state/guided-tours/contexts';
import { isCurrentUserEmailVerified } from 'state/current-user/selectors';

export default {
Expand Down
6 changes: 1 addition & 5 deletions client/layout/guided-tours/tours/main-tour/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* External dependencies
*/

import React, { Fragment } from 'react';

/**
Expand All @@ -18,10 +17,7 @@ import {
Continue,
Link,
} from 'layout/guided-tours/config-elements';
import {
isSelectedSitePreviewable,
isSelectedSiteCustomizable,
} from 'state/ui/guided-tours/contexts';
import { isSelectedSitePreviewable, isSelectedSiteCustomizable } from 'state/guided-tours/contexts';
import { getScrollableSidebar } from 'layout/guided-tours/positioning';
import scrollTo from 'lib/scroll-to';
import { ViewSiteButton } from 'layout/guided-tours/button-labels';
Expand Down
2 changes: 1 addition & 1 deletion client/layout/guided-tours/tours/main-tour/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { isNewUser, isEnabled } from 'state/ui/guided-tours/contexts';
import { isNewUser, isEnabled } from 'state/guided-tours/contexts';

export default {
name: 'main',
Expand Down
3 changes: 1 addition & 2 deletions client/layout/guided-tours/tours/media-basics-tour/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* External dependencies
*/

import React, { Fragment } from 'react';

/**
Expand All @@ -17,7 +16,7 @@ import {
Quit,
Continue,
} from 'layout/guided-tours/config-elements';
import { doesSelectedSiteHaveMediaFiles } from 'state/ui/guided-tours/contexts';
import { doesSelectedSiteHaveMediaFiles } from 'state/guided-tours/contexts';
import {
AddNewButton,
EditButton,
Expand Down
2 changes: 1 addition & 1 deletion client/layout/guided-tours/tours/media-basics-tour/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isDesktop } from '@automattic/viewport';
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { isNewUser } from 'state/ui/guided-tours/contexts';
import { isNewUser } from 'state/guided-tours/contexts';

export default {
name: 'mediaBasicsTour',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isDesktop } from '@automattic/viewport';
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { isNotNewUser } from 'state/ui/guided-tours/contexts';
import { isNotNewUser } from 'state/guided-tours/contexts';
import { isCurrentUserEmailVerified } from 'state/current-user/selectors';

export default {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Internal dependencies
*/
import { and } from 'layout/guided-tours/utils';
import { isNewUser, isEnabled, isSelectedSitePreviewable } from 'state/ui/guided-tours/contexts';
import { isNewUser, isEnabled, isSelectedSitePreviewable } from 'state/guided-tours/contexts';

export default {
name: 'tutorialSitePreview',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import getSiteChecklist from 'state/selectors/get-site-checklist';
import isUnlaunchedSite from 'state/selectors/is-unlaunched-site';
import getMenusUrl from 'state/selectors/get-menus-url';
import { getSiteOption, getSiteSlug } from 'state/sites/selectors';
import { requestGuidedTour } from 'state/ui/guided-tours/actions';
import { requestGuidedTour } from 'state/guided-tours/actions';
import { getSelectedSiteId } from 'state/ui/selectors';
import { skipCurrentViewHomeLayout } from 'state/home/actions';
import NavItem from './nav-item';
Expand Down
4 changes: 2 additions & 2 deletions client/my-sites/media-library/content.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import {
isKeyringConnectionsFetching,
getKeyringConnectionsByName,
} from 'state/sharing/keyring/selectors';
import { pauseGuidedTour, resumeGuidedTour } from 'state/ui/guided-tours/actions';
import { pauseGuidedTour, resumeGuidedTour } from 'state/guided-tours/actions';
import { deleteKeyringConnection } from 'state/sharing/keyring/actions';
import { getGuidedTourState } from 'state/ui/guided-tours/selectors';
import { getGuidedTourState } from 'state/guided-tours/selectors';
import { withoutNotice } from 'state/notices/actions';
import { clearMediaErrors, changeMediaSource } from 'state/media/actions';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { get } from 'lodash';
* Internal dependencies
*/
import { preventWidows } from 'lib/formatting';
import { requestGuidedTour } from 'state/ui/guided-tours/actions';
import { requestGuidedTour } from 'state/guided-tours/actions';
import { Button } from '@automattic/components';
import getCurrentQueryArguments from 'state/selectors/get-current-query-arguments';
import getCurrentRoute from 'state/selectors/get-current-route';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { format as formatUrl, parse as parseUrl } from 'url';
import { getSelectedSite, getSelectedSiteId } from 'state/ui/selectors';
import { getSiteSlug, getCustomizerUrl, getSiteProducts } from 'state/sites/selectors';
import { recordTracksEvent } from 'state/analytics/actions';
import { requestGuidedTour } from 'state/ui/guided-tours/actions';
import { requestGuidedTour } from 'state/guided-tours/actions';
import { URL } from 'types';
import { getSitePlanSlug } from 'state/sites/plans/selectors';
import { isBusinessPlan, isPremiumPlan } from 'lib/plans';
Expand Down
2 changes: 1 addition & 1 deletion client/my-sites/plugins/plugin-meta/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jest.mock( 'lib/plugins/wporg-data/list-store', () => ( {
getSearchList: () => {},
on: () => {},
} ) );
jest.mock( 'state/ui/guided-tours/selectors', () => ( {} ) );
jest.mock( 'state/guided-tours/selectors', () => ( {} ) );
jest.mock( 'my-sites/plugins/utils', () => ( {
getExtensionSettingsPath: () => '',
} ) );
Expand Down
3 changes: 1 addition & 2 deletions client/my-sites/plugins/plugin.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* External dependencies
*/

import React from 'react';
import createReactClass from 'create-react-class';
import { localize } from 'i18n-calypso';
Expand Down Expand Up @@ -36,7 +35,7 @@ import canCurrentUserManagePlugins from 'state/selectors/can-current-user-manage
import getSelectedOrAllSitesWithPlugins from 'state/selectors/get-selected-or-all-sites-with-plugins';
import isSiteAutomatedTransfer from 'state/selectors/is-site-automated-transfer';
import NoPermissionsError from './no-permissions-error';
import getToursHistory from 'state/ui/guided-tours/selectors/get-tours-history';
import getToursHistory from 'state/guided-tours/selectors/get-tours-history';
import hasNavigated from 'state/selectors/has-navigated';

/* eslint-disable react/prefer-es6-class */
Expand Down
2 changes: 1 addition & 1 deletion client/post-editor/editor-ground-control/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import isUnlaunchedSite from 'state/selectors/is-unlaunched-site';
import isVipSite from 'state/selectors/is-vip-site';
import { isCurrentUserEmailVerified } from 'state/current-user/selectors';
import { getRouteHistory } from 'state/ui/action-log/selectors';
import { pauseGuidedTour } from 'state/ui/guided-tours/actions';
import { pauseGuidedTour } from 'state/guided-tours/actions';

/**
* Style dependencies
Expand Down
2 changes: 1 addition & 1 deletion client/post-editor/post-editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ import { removep } from 'lib/formatting';
import QuickSaveButtons from 'post-editor/editor-ground-control/quick-save-buttons';
import EditorRevisionsDialog from 'post-editor/editor-revisions/dialog';
import PageViewTracker from 'lib/analytics/page-view-tracker';
import { pauseGuidedTour } from 'state/ui/guided-tours/actions';
import { pauseGuidedTour } from 'state/guided-tours/actions';
import inEditorDeprecationGroup from 'state/editor-deprecation-group/selectors/in-editor-deprecation-group';
import { isEnabled } from 'config';

Expand Down
2 changes: 1 addition & 1 deletion client/reader/following/intro.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import QueryPreferences from 'components/data/query-preferences';
import { savePreference } from 'state/preferences/actions';
import { getPreference } from 'state/preferences/selectors';
import { recordTrack } from 'reader/stats';
import { isUserNewerThan, WEEK_IN_MILLISECONDS } from 'state/ui/guided-tours/contexts';
import { isUserNewerThan, WEEK_IN_MILLISECONDS } from 'state/guided-tours/contexts';
import cssSafeUrl from 'lib/css-safe-url';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Internal dependencies
*/

import { GUIDED_TOUR_UPDATE, GUIDED_TOUR_PAUSE, GUIDED_TOUR_RESUME } from 'state/action-types';

import { savePreference } from 'state/preferences/actions';
import { getPreference } from 'state/preferences/selectors';

import 'state/guided-tours/init';

export function quitGuidedTour( { tour, stepName, finished } ) {
const quitAction = {
type: GUIDED_TOUR_UPDATE,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Internal dependencies
*/
import { getSelectedSiteId } from 'state/ui/selectors';
import canCurrentUser from 'state/selectors/can-current-user';

/**
* Returns true if the current user can edit settings of the selected site.
* Used in the siteTitle tour.
*
* @param {object} state Global state tree
* @returns {boolean} True if user can edit settings, false otherwise.
*/
export const canUserEditSettingsOfSelectedSite = ( state ) => {
const siteId = getSelectedSiteId( state );
return siteId ? canCurrentUser( state, siteId, 'manage_options' ) : false;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Internal dependencies
*/
import MediaStore from 'lib/media/store';
import { getSelectedSiteId } from 'state/ui/selectors';

const { getAll: getAllMedia } = MediaStore;

/**
* Returns true if the selected site has any media files.
*
* @param {object} state Global state tree
* @returns {boolean} True if site has any media files, false otherwise.
*/
export const doesSelectedSiteHaveMediaFiles = ( state ) => {
const siteId = getSelectedSiteId( state );
if ( ! siteId ) {
return false;
}
const media = getAllMedia( siteId );
return media && media.length && media.length > 0;
};
22 changes: 22 additions & 0 deletions client/state/guided-tours/contexts/has-analytics-event-fired.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Internal dependencies
*/
import { ANALYTICS_EVENT_RECORD } from 'state/action-types';
import { getLastAction } from 'state/ui/action-log/selectors';

/**
* Returns a selector that tests whether a certain analytics event has been
* fired.
*
* @see client/state/analytics
*
* @param {string} eventName Name of analytics event
* @returns {Function} Selector function
*/
export const hasAnalyticsEventFired = ( eventName ) => ( state ) => {
const last = getLastAction( state );
return (
last.type === ANALYTICS_EVENT_RECORD &&
last.meta.analytics.some( ( record ) => record.payload.name === eventName )
);
};