mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Enable auto triggering of Welcome Tour, and various improvements.
Summary: - Add showGristTour preference, and trigger tour automatically. - Tour is only triggered for new and anonymous users on a personal org, with edit permission. - Automatically open the right panel at tour start. - Don't show tours on mobile, since that's not ready (popups are cut off and can't be dismissed) - Cancel previous tour if a new one is somehow started. - Remove #repeat- trigger hash tags from the URL when the tour starts. - Ensure Help Center popup is positioned even when left panel is collapsed. - Polish up the content of the last two cards in the tour. Test Plan: Added test case for triggering and opening right panel. Reviewers: alexmojaki, paulfitz Reviewed By: alexmojaki Differential Revision: https://phab.getgrist.com/D2955
This commit is contained in:
parent
73c4efa315
commit
1605e18f66
@ -29,6 +29,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel';
|
|||||||
import {UserError} from 'app/client/models/errors';
|
import {UserError} from 'app/client/models/errors';
|
||||||
import {urlState} from 'app/client/models/gristUrlState';
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
import {QuerySetManager} from 'app/client/models/QuerySet';
|
import {QuerySetManager} from 'app/client/models/QuerySet';
|
||||||
|
import {getUserOrgPrefObs} from "app/client/models/UserPrefs";
|
||||||
import {App} from 'app/client/ui/App';
|
import {App} from 'app/client/ui/App';
|
||||||
import {DocHistory} from 'app/client/ui/DocHistory';
|
import {DocHistory} from 'app/client/ui/DocHistory';
|
||||||
import {showDocSettingsModal} from 'app/client/ui/DocumentSettings';
|
import {showDocSettingsModal} from 'app/client/ui/DocumentSettings';
|
||||||
@ -36,7 +37,7 @@ import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
|||||||
import {IPageWidgetLink, linkFromId, selectBy} from 'app/client/ui/selectBy';
|
import {IPageWidgetLink, linkFromId, selectBy} from 'app/client/ui/selectBy';
|
||||||
import {startWelcomeTour} from 'app/client/ui/welcomeTour';
|
import {startWelcomeTour} from 'app/client/ui/welcomeTour';
|
||||||
import {startDocTour} from "app/client/ui/DocTour";
|
import {startDocTour} from "app/client/ui/DocTour";
|
||||||
import {mediaSmall, testId} from 'app/client/ui2018/cssVars';
|
import {isNarrowScreen, mediaSmall, testId} from 'app/client/ui2018/cssVars';
|
||||||
import {IconName} from 'app/client/ui2018/IconList';
|
import {IconName} from 'app/client/ui2018/IconList';
|
||||||
import {ActionGroup} from 'app/common/ActionGroup';
|
import {ActionGroup} from 'app/common/ActionGroup';
|
||||||
import {delay} from 'app/common/delay';
|
import {delay} from 'app/common/delay';
|
||||||
@ -135,6 +136,7 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
private _docHistory: DocHistory;
|
private _docHistory: DocHistory;
|
||||||
private _rightPanelTool = createSessionObs(this, "rightPanelTool", "none", RightPanelTool.guard);
|
private _rightPanelTool = createSessionObs(this, "rightPanelTool", "none", RightPanelTool.guard);
|
||||||
private _viewLayout: ViewLayout|null = null;
|
private _viewLayout: ViewLayout|null = null;
|
||||||
|
private _showGristTour = getUserOrgPrefObs(this.docPageModel.appModel, 'showGristTour');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly app: App,
|
public readonly app: App,
|
||||||
@ -209,19 +211,21 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
|
|
||||||
// Start welcome tour if flag is present in the url hash.
|
// Start welcome tour if flag is present in the url hash.
|
||||||
this.autoDispose(subscribe(urlState().state, async (_use, state) => {
|
this.autoDispose(subscribe(urlState().state, async (_use, state) => {
|
||||||
if (state.welcomeTour || state.docTour) {
|
if (state.welcomeTour || state.docTour || this._shouldAutoStartWelcomeTour()) {
|
||||||
|
// On boarding tours were not designed with mobile support in mind. Disable until fixed.
|
||||||
|
if (isNarrowScreen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
await this._waitForView();
|
await this._waitForView();
|
||||||
await delay(0); // we need to wait an extra bit.
|
await delay(0); // we need to wait an extra bit.
|
||||||
// TODO:
|
|
||||||
// 1) url needs cleanup, #repeat-welcome-tour sticks to it and so even when navigating
|
// Remove any tour-related hash-tags from the URL. So #repeat-welcome-tour and
|
||||||
// to home page. This could eventually become an issue: if user opens another document it
|
// #repeat-doc-tour are used as triggers, but will immediately disappear.
|
||||||
// would starts the onboarding tour again.
|
await urlState().pushUrl({welcomeTour: false, docTour: false},
|
||||||
// 2) Makes sure the right panel is opened with the Column tab selected. Because some
|
{replace: true, avoidReload: true});
|
||||||
// of the messages relates to that part of the UI.
|
|
||||||
// 3) On boarding tours were not designed with mobile support in mind. So probably a
|
if (!state.docTour) {
|
||||||
// good idea to disable.
|
startWelcomeTour(() => this._showGristTour.set(false));
|
||||||
if (state.welcomeTour) {
|
|
||||||
startWelcomeTour(() => null);
|
|
||||||
} else {
|
} else {
|
||||||
await startDocTour(this.docData, () => null);
|
await startDocTour(this.docData, () => null);
|
||||||
}
|
}
|
||||||
@ -865,6 +869,24 @@ export class GristDoc extends DisposableWithEvents {
|
|||||||
}
|
}
|
||||||
return cursorPos;
|
return cursorPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For first-time users on personal org, start a welcome tour.
|
||||||
|
*/
|
||||||
|
private _shouldAutoStartWelcomeTour(): boolean {
|
||||||
|
// TODO: decide what to do when both a docTour and grist welcome tour are available.
|
||||||
|
|
||||||
|
// Only show the tour if one is on a personal org and can edit. This excludes templates (on
|
||||||
|
// the Templates org, which may have their own tour) and team sites (where user's intended
|
||||||
|
// role is often other than document creator).
|
||||||
|
const appModel = this.docPageModel.appModel;
|
||||||
|
if (!appModel.currentOrg?.owner || this.isReadonly.get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Use the showGristTour pref if set; otherwise default to true for anonymous users, and false
|
||||||
|
// for real returning users.
|
||||||
|
return this._showGristTour.get() ?? (!appModel.currentValidUser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function finalizeAnchor() {
|
async function finalizeAnchor() {
|
||||||
|
@ -30,17 +30,17 @@ export function createHelpTools(appModel: AppModel, spacer = true): DomContents
|
|||||||
spacer ? cssSpacer() : null,
|
spacer ? cssSpacer() : null,
|
||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
cssPageLink(cssPageIcon('Feedback'),
|
cssPageLink(cssPageIcon('Feedback'),
|
||||||
cssLinkText('Give Feedback', dom.cls('tour-feedback')),
|
cssLinkText('Give Feedback'),
|
||||||
dom.on('click', () => beaconOpenMessage({appModel})),
|
dom.on('click', () => beaconOpenMessage({appModel})),
|
||||||
),
|
),
|
||||||
dom.hide(isEfcr),
|
dom.hide(isEfcr),
|
||||||
testId('left-feedback'),
|
testId('left-feedback'),
|
||||||
),
|
),
|
||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
cssPageLink(cssPageIcon('Help'), {href: commonUrls.help, target: '_blank'}, cssLinkText(
|
cssPageLink(cssPageIcon('Help'), {href: commonUrls.help, target: '_blank'},
|
||||||
'Help Center',
|
cssLinkText('Help Center'),
|
||||||
dom.cls('tour-help-center')
|
dom.cls('tour-help-center')
|
||||||
)),
|
),
|
||||||
dom.hide(isEfcr),
|
dom.hide(isEfcr),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
* the caller. Pass an `onFinishCB` to handle when a user dimiss the popups.
|
* the caller. Pass an `onFinishCB` to handle when a user dimiss the popups.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Disposable, dom, DomElementArg, makeTestId, styled, svg } from "grainjs";
|
import { Disposable, dom, DomElementArg, Holder, makeTestId, styled, svg } from "grainjs";
|
||||||
import { createPopper, Placement } from '@popperjs/core';
|
import { createPopper, Placement } from '@popperjs/core';
|
||||||
import { FocusLayer } from 'app/client/lib/FocusLayer';
|
import { FocusLayer } from 'app/client/lib/FocusLayer';
|
||||||
import * as Mousetrap from 'app/client/lib/Mousetrap';
|
import * as Mousetrap from 'app/client/lib/Mousetrap';
|
||||||
@ -71,8 +71,12 @@ export interface IOnBoardingMsg {
|
|||||||
urlState?: IGristUrlState;
|
urlState?: IGristUrlState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There should only be one tour at a time. Use a holder to dispose the previous tour when
|
||||||
|
// starting a new one.
|
||||||
|
const tourSingleton = Holder.create<OnBoardingPopupsCtl>(null);
|
||||||
|
|
||||||
export function startOnBoarding(messages: IOnBoardingMsg[], onFinishCB: () => void) {
|
export function startOnBoarding(messages: IOnBoardingMsg[], onFinishCB: () => void) {
|
||||||
const ctl = new OnBoardingPopupsCtl(messages, onFinishCB);
|
const ctl = OnBoardingPopupsCtl.create(tourSingleton, messages, onFinishCB);
|
||||||
ctl.start().catch(reportError);
|
ctl.start().catch(reportError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ export class RightPanel extends Disposable {
|
|||||||
|
|
||||||
private _buildFieldContent(owner: MultiHolder) {
|
private _buildFieldContent(owner: MultiHolder) {
|
||||||
const fieldBuilder = owner.autoDispose(ko.computed(() => {
|
const fieldBuilder = owner.autoDispose(ko.computed(() => {
|
||||||
const vsi = this._gristDoc.viewModel.activeSection().viewInstance();
|
const vsi = this._gristDoc.viewModel.activeSection?.().viewInstance();
|
||||||
return vsi && vsi.activeFieldBuilder();
|
return vsi && vsi.activeFieldBuilder();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ export class RightPanel extends Disposable {
|
|||||||
|
|
||||||
// build cursor position observable
|
// build cursor position observable
|
||||||
const cursor = owner.autoDispose(ko.computed(() => {
|
const cursor = owner.autoDispose(ko.computed(() => {
|
||||||
const vsi = this._gristDoc.viewModel.activeSection().viewInstance();
|
const vsi = this._gristDoc.viewModel.activeSection?.().viewInstance();
|
||||||
return vsi?.cursor.currentPosition() ?? {};
|
return vsi?.cursor.currentPosition() ?? {};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import * as commands from 'app/client/components/commands';
|
||||||
|
import { urlState } from 'app/client/models/gristUrlState';
|
||||||
import { IOnBoardingMsg, startOnBoarding } from "app/client/ui/OnBoardingPopups";
|
import { IOnBoardingMsg, startOnBoarding } from "app/client/ui/OnBoardingPopups";
|
||||||
import { colors } from 'app/client/ui2018/cssVars';
|
import { colors } from 'app/client/ui2018/cssVars';
|
||||||
import { icon } from "app/client/ui2018/icons";
|
import { icon } from "app/client/ui2018/icons";
|
||||||
|
import { cssLink } from "app/client/ui2018/links";
|
||||||
import { dom, styled } from "grainjs";
|
import { dom, styled } from "grainjs";
|
||||||
|
|
||||||
export const welcomeTour: IOnBoardingMsg[] = [
|
export const welcomeTour: IOnBoardingMsg[] = [
|
||||||
@ -58,30 +61,30 @@ export const welcomeTour: IOnBoardingMsg[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: '.tour-help-center',
|
selector: '.tour-help-center',
|
||||||
title: 'Keep learning',
|
title: 'Flying higher',
|
||||||
body: () => [
|
body: () => [
|
||||||
dom('p', 'Unlock Grist\'s hidden power. Dive into our documentation, videos, ',
|
dom('p', 'Use ', Key(GreyIcon('Help'), 'Help Center'), ' for documentation, videos, and tutorials.'),
|
||||||
'and tutorials to take your spreadsheet-database to the next level. '),
|
dom('p', 'Use ', Key(GreyIcon('Feedback'), 'Give Feedback'), ' for issues or questions.'),
|
||||||
],
|
|
||||||
placement: 'right',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
selector: '.tour-feedback',
|
|
||||||
title: 'Give feedback',
|
|
||||||
body: () => [
|
|
||||||
dom('p', 'Use ', Key('Give Feedback'), ' button (', Icon('Feedback'), ') for issues or questions. '),
|
|
||||||
],
|
],
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: '.tour-welcome',
|
selector: '.tour-welcome',
|
||||||
title: 'Welcome to Grist!',
|
title: 'Welcome to Grist!',
|
||||||
|
body: () => [
|
||||||
|
dom('p', 'Browse our ',
|
||||||
|
cssLink({target: '_blank', href: urlState().makeUrl({homePage: "templates"})},
|
||||||
|
'template library', cssInlineIcon('FieldLink')),
|
||||||
|
"to discover what's possible and get inspired."
|
||||||
|
),
|
||||||
|
],
|
||||||
showHasModal: true,
|
showHasModal: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function startWelcomeTour(onFinishCB: () => void) {
|
export function startWelcomeTour(onFinishCB: () => void) {
|
||||||
|
commands.allCommands.fieldTabOpen.run();
|
||||||
startOnBoarding(welcomeTour, onFinishCB);
|
startOnBoarding(welcomeTour, onFinishCB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +98,8 @@ const KeyStrong = styled(KeyContent, `
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const Key = styled('code', `
|
const Key = styled('div', `
|
||||||
|
display: inline-block;
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0px 2px;
|
margin: 0px 2px;
|
||||||
@ -110,3 +114,12 @@ const Key = styled('code', `
|
|||||||
const Icon = styled(icon, `
|
const Icon = styled(icon, `
|
||||||
--icon-color: ${colors.lightGreen};
|
--icon-color: ${colors.lightGreen};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const GreyIcon = styled(icon, `
|
||||||
|
--icon-color: ${colors.slate};
|
||||||
|
margin-right: 8px;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const cssInlineIcon = styled(icon, `
|
||||||
|
margin: -3px 8px 0 4px;
|
||||||
|
`);
|
||||||
|
@ -168,7 +168,7 @@ export const mediaXSmall = `(max-width: ${smallScreenWidth - 0.02}px)`;
|
|||||||
|
|
||||||
export const mediaDeviceNotSmall = `(min-device-width: ${mediumScreenWidth}px)`;
|
export const mediaDeviceNotSmall = `(min-device-width: ${mediumScreenWidth}px)`;
|
||||||
|
|
||||||
function isNarrowScreen() {
|
export function isNarrowScreen() {
|
||||||
return window.innerWidth < mediumScreenWidth;
|
return window.innerWidth < mediumScreenWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,11 @@ export interface UserOrgPrefs extends Prefs {
|
|||||||
// By living in UserOrgPrefs, this applies only to the examples-containing org.
|
// By living in UserOrgPrefs, this applies only to the examples-containing org.
|
||||||
seenExamples?: number[];
|
seenExamples?: number[];
|
||||||
|
|
||||||
|
// Whether the user should see the onboarding tour of Grist. False by default, since existing
|
||||||
|
// users should not see it. New users get this set to true when the user is created. This
|
||||||
|
// applies to the personal org only; the tour is currently only shown there.
|
||||||
|
showGristTour?: boolean;
|
||||||
|
|
||||||
// List of document IDs where the user has seen and dismissed the document tour.
|
// List of document IDs where the user has seen and dismissed the document tour.
|
||||||
seenDocTours?: string[];
|
seenDocTours?: string[];
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,12 @@ export function encodeUrl(gristConfig: Partial<GristLoadConfig>,
|
|||||||
if (state.hash) {
|
if (state.hash) {
|
||||||
// Project tests use hashes, so only set hash if there is an anchor.
|
// Project tests use hashes, so only set hash if there is an anchor.
|
||||||
url.hash = hashParts.join('.');
|
url.hash = hashParts.join('.');
|
||||||
|
} else if (state.welcomeTour) {
|
||||||
|
url.hash = 'repeat-welcome-tour';
|
||||||
|
} else if (state.docTour) {
|
||||||
|
url.hash = 'repeat-doc-tour';
|
||||||
|
} else {
|
||||||
|
url.hash = '';
|
||||||
}
|
}
|
||||||
return url.href;
|
return url.href;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import {canAddOrgMembers, Features} from 'app/common/Features';
|
|||||||
import {buildUrlId, MIN_URLID_PREFIX_LENGTH, parseUrlId} from 'app/common/gristUrls';
|
import {buildUrlId, MIN_URLID_PREFIX_LENGTH, parseUrlId} from 'app/common/gristUrls';
|
||||||
import {FullUser, UserProfile} from 'app/common/LoginSessionAPI';
|
import {FullUser, UserProfile} from 'app/common/LoginSessionAPI';
|
||||||
import {checkSubdomainValidity} from 'app/common/orgNameUtils';
|
import {checkSubdomainValidity} from 'app/common/orgNameUtils';
|
||||||
|
import {UserOrgPrefs} from 'app/common/Prefs';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
// TODO: API should implement UserAPI
|
// TODO: API should implement UserAPI
|
||||||
import {ANONYMOUS_USER_EMAIL, DocumentProperties, EVERYONE_EMAIL,
|
import {ANONYMOUS_USER_EMAIL, DocumentProperties, EVERYONE_EMAIL,
|
||||||
@ -531,6 +532,13 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
throw new Error(result.errMessage);
|
throw new Error(result.errMessage);
|
||||||
}
|
}
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
|
|
||||||
|
// We just created a personal org; set userOrgPrefs that should apply for new users only.
|
||||||
|
const userOrgPrefs: UserOrgPrefs = {showGristTour: true};
|
||||||
|
const orgId = result.data;
|
||||||
|
if (orgId) {
|
||||||
|
await this.updateOrg({userId: user.id}, orgId, {userOrgPrefs}, manager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (needUpdate) {
|
if (needUpdate) {
|
||||||
// We changed the db - reload user in order to give consistent results.
|
// We changed the db - reload user in order to give consistent results.
|
||||||
@ -1201,7 +1209,8 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
public async updateOrg(
|
public async updateOrg(
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
orgKey: string|number,
|
orgKey: string|number,
|
||||||
props: Partial<OrganizationProperties>
|
props: Partial<OrganizationProperties>,
|
||||||
|
transaction?: EntityManager,
|
||||||
): Promise<QueryResult<number>> {
|
): Promise<QueryResult<number>> {
|
||||||
|
|
||||||
// Check the scope of the modifications.
|
// Check the scope of the modifications.
|
||||||
@ -1224,7 +1233,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Unsetting a domain will likely have to be supported; also possibly prefs.
|
// TODO: Unsetting a domain will likely have to be supported; also possibly prefs.
|
||||||
return await this._connection.transaction(async manager => {
|
return await this._runInTransaction(transaction, async manager => {
|
||||||
const orgQuery = this.org(scope, orgKey, {
|
const orgQuery = this.org(scope, orgKey, {
|
||||||
manager,
|
manager,
|
||||||
markPermissions,
|
markPermissions,
|
||||||
|
@ -37,6 +37,7 @@ describe("Smoke", function() {
|
|||||||
await openMainPage();
|
await openMainPage();
|
||||||
await driver.findContent('button', /Create Empty Document/).click();
|
await driver.findContent('button', /Create Empty Document/).click();
|
||||||
await gu.waitForDocToLoad(20000);
|
await gu.waitForDocToLoad(20000);
|
||||||
|
await gu.dismissWelcomeTourIfNeeded();
|
||||||
await gu.getCell('A', 1).click();
|
await gu.getCell('A', 1).click();
|
||||||
await gu.enterCell('123');
|
await gu.enterCell('123');
|
||||||
await driver.navigate().refresh();
|
await driver.navigate().refresh();
|
||||||
|
@ -154,6 +154,15 @@ export async function waitForUrl(pattern: RegExp|string, waitMs: number = 2000)
|
|||||||
await driver.wait(() => testCurrentUrl(pattern), waitMs);
|
await driver.wait(() => testCurrentUrl(pattern), waitMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function dismissWelcomeTourIfNeeded() {
|
||||||
|
const elem = driver.find('.test-onboarding-close');
|
||||||
|
if (await elem.isPresent()) {
|
||||||
|
await elem.click();
|
||||||
|
}
|
||||||
|
await waitForServer();
|
||||||
|
}
|
||||||
|
|
||||||
// Selects all text when a text element is currently active.
|
// Selects all text when a text element is currently active.
|
||||||
export async function selectAll() {
|
export async function selectAll() {
|
||||||
await driver.executeScript('document.activeElement.select()');
|
await driver.executeScript('document.activeElement.select()');
|
||||||
|
@ -52,14 +52,17 @@ export class HomeUtil {
|
|||||||
loginMethod?: UserProfile['loginMethod'],
|
loginMethod?: UserProfile['loginMethod'],
|
||||||
freshAccount?: boolean,
|
freshAccount?: boolean,
|
||||||
isFirstLogin?: boolean,
|
isFirstLogin?: boolean,
|
||||||
|
showGristTour?: boolean,
|
||||||
cacheCredentials?: boolean,
|
cacheCredentials?: boolean,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const {loginMethod, isFirstLogin} = defaults(options, {loginMethod: 'Email + Password'});
|
const {loginMethod, isFirstLogin} = defaults(options, {loginMethod: 'Email + Password'});
|
||||||
|
const showGristTour = options.showGristTour ?? (options.freshAccount ?? isFirstLogin);
|
||||||
|
|
||||||
// For regular tests, we can log in through a testing hook.
|
// For regular tests, we can log in through a testing hook.
|
||||||
if (!this.server.isExternalServer()) {
|
if (!this.server.isExternalServer()) {
|
||||||
if (options.freshAccount) { await this._deleteUserByEmail(email); }
|
if (options.freshAccount) { await this._deleteUserByEmail(email); }
|
||||||
if (isFirstLogin !== undefined) { await this._setFirstLogin(email, isFirstLogin); }
|
if (isFirstLogin !== undefined) { await this._setFirstLogin(email, isFirstLogin); }
|
||||||
|
if (showGristTour !== undefined) { await this._initShowGristTour(email, showGristTour); }
|
||||||
// TestingHooks communicates via JSON, so it's impossible to send an `undefined` value for org
|
// TestingHooks communicates via JSON, so it's impossible to send an `undefined` value for org
|
||||||
// through it. Using the empty string happens to work though.
|
// through it. Using the empty string happens to work though.
|
||||||
const testingHooks = await this.server.getTestingHooks();
|
const testingHooks = await this.server.getTestingHooks();
|
||||||
@ -86,6 +89,9 @@ export class HomeUtil {
|
|||||||
await this._fillWelcomePageIfPresent(name);
|
await this._fillWelcomePageIfPresent(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (options.freshAccount) {
|
||||||
|
this._apiKey.delete(email);
|
||||||
|
}
|
||||||
if (options.cacheCredentials) {
|
if (options.cacheCredentials) {
|
||||||
// Take this opportunity to cache access info.
|
// Take this opportunity to cache access info.
|
||||||
if (!this._apiKey.has(email)) {
|
if (!this._apiKey.has(email)) {
|
||||||
@ -302,6 +308,16 @@ export class HomeUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _initShowGristTour(email: string, showGristTour: boolean) {
|
||||||
|
if (this.server.isExternalServer()) { throw new Error('not supported'); }
|
||||||
|
const dbManager = await this.server.getDatabase();
|
||||||
|
const user = await dbManager.getUserByLogin(email);
|
||||||
|
if (user && user.personalOrg) {
|
||||||
|
const userOrgPrefs = {showGristTour};
|
||||||
|
await dbManager.updateOrg({userId: user.id}, user.personalOrg.id, {userOrgPrefs});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get past the user welcome page if it is present.
|
// Get past the user welcome page if it is present.
|
||||||
private async _fillWelcomePageIfPresent(name?: string) {
|
private async _fillWelcomePageIfPresent(name?: string) {
|
||||||
// TODO: check treatment of welcome/team page when necessary.
|
// TODO: check treatment of welcome/team page when necessary.
|
||||||
|
Loading…
Reference in New Issue
Block a user