mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add GRIST_UI_FEATURES env variable
Summary: Tutorials are now hidden by default in grist-core and grist-ee, and can be re-enabled via a new env variable, GRIST_UI_FEATURES, which accepts a comma-separated list of UI features to enable. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Subscribers: jarek Differential Revision: https://phab.getgrist.com/D3885
This commit is contained in:
parent
1e873b4203
commit
f18bb3e39d
@ -260,7 +260,7 @@ GRIST_DEFAULT_PRODUCT | if set, this controls enabled features and limits of ne
|
|||||||
GRIST_DEFAULT_LOCALE | Locale to use as fallback when Grist cannot honour the browser locale.
|
GRIST_DEFAULT_LOCALE | Locale to use as fallback when Grist cannot honour the browser locale.
|
||||||
GRIST_DOMAIN | in hosted Grist, Grist is served from subdomains of this domain. Defaults to "getgrist.com".
|
GRIST_DOMAIN | in hosted Grist, Grist is served from subdomains of this domain. Defaults to "getgrist.com".
|
||||||
GRIST_EXPERIMENTAL_PLUGINS | enables experimental plugins
|
GRIST_EXPERIMENTAL_PLUGINS | enables experimental plugins
|
||||||
GRIST_HIDE_UI_ELEMENTS | comma-separated list of parts of the UI to hide. Allowed names of parts: `helpCenter,billing,templates,multiSite,multiAccounts,sendToDrive`
|
GRIST_HIDE_UI_ELEMENTS | comma-separated list of UI features to disable. Allowed names of parts: `helpCenter,billing,templates,multiSite,multiAccounts,sendToDrive,tutorials`. If a part also exists in GRIST_UI_FEATURES, it will still be disabled.
|
||||||
GRIST_HOME_INCLUDE_STATIC | if set, home server also serves static resources
|
GRIST_HOME_INCLUDE_STATIC | if set, home server also serves static resources
|
||||||
GRIST_HOST | hostname to use when listening on a port.
|
GRIST_HOST | hostname to use when listening on a port.
|
||||||
GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*.
|
GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*.
|
||||||
@ -286,6 +286,7 @@ GRIST_SUPPORT_ANON | if set to 'true', show UI for anonymous access (not shown b
|
|||||||
GRIST_SUPPORT_EMAIL | if set, give a user with the specified email support powers. The main extra power is the ability to share sites, workspaces, and docs with all users in a listed way.
|
GRIST_SUPPORT_EMAIL | if set, give a user with the specified email support powers. The main extra power is the ability to share sites, workspaces, and docs with all users in a listed way.
|
||||||
GRIST_THROTTLE_CPU | if set, CPU throttling is enabled
|
GRIST_THROTTLE_CPU | if set, CPU throttling is enabled
|
||||||
GRIST_USER_ROOT | an extra path to look for plugins in.
|
GRIST_USER_ROOT | an extra path to look for plugins in.
|
||||||
|
GRIST_UI_FEATURES | comma-separated list of UI features to enable. Allowed names of parts: `helpCenter,billing,templates,multiSite,multiAccounts,sendToDrive,tutorials`. If a part also exists in GRIST_HIDE_UI_ELEMENTS, it won't be enabled.
|
||||||
GRIST_WIDGET_LIST_URL | a url pointing to a widget manifest
|
GRIST_WIDGET_LIST_URL | a url pointing to a widget manifest
|
||||||
COOKIE_MAX_AGE | session cookie max age, defaults to 90 days; can be set to "none" to make it a session cookie
|
COOKIE_MAX_AGE | session cookie max age, defaults to 90 days; can be set to "none" to make it a session cookie
|
||||||
HOME_PORT | port number to listen on for REST API server; if set to "share", add API endpoints to regular grist port.
|
HOME_PORT | port number to listen on for REST API server; if set to "share", add API endpoints to regular grist port.
|
||||||
|
@ -9,7 +9,7 @@ import {primaryButton} from 'app/client/ui2018/buttons';
|
|||||||
import {mediaDeviceNotSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
import {mediaDeviceNotSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {menu, menuDivider, menuItem, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus';
|
import {menu, menuDivider, menuItem, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus';
|
||||||
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
|
import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {Disposable, dom, DomElementArg, styled} from 'grainjs';
|
import {Disposable, dom, DomElementArg, styled} from 'grainjs';
|
||||||
@ -107,7 +107,7 @@ export class AccountWidget extends Disposable {
|
|||||||
|
|
||||||
// In case of a single-org setup, skip all the account-switching UI. We'll also skip the
|
// In case of a single-org setup, skip all the account-switching UI. We'll also skip the
|
||||||
// org-listing UI below.
|
// org-listing UI below.
|
||||||
this._appModel.topAppModel.isSingleOrg || shouldHideUiElement("multiAccounts") ? [] : [
|
this._appModel.topAppModel.isSingleOrg || !isFeatureEnabled("multiAccounts") ? [] : [
|
||||||
menuDivider(),
|
menuDivider(),
|
||||||
menuSubHeader(dom.text((use) => use(users).length > 1 ? t("Switch Accounts") : t("Accounts"))),
|
menuSubHeader(dom.text((use) => use(users).length > 1 ? t("Switch Accounts") : t("Accounts"))),
|
||||||
dom.forEach(users, (_user) => {
|
dom.forEach(users, (_user) => {
|
||||||
|
@ -9,7 +9,7 @@ import {bigBasicButton, cssButton} from 'app/client/ui2018/buttons';
|
|||||||
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
import {testId, theme, vars} 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 {cssLink} from 'app/client/ui2018/links';
|
||||||
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
|
import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {Computed, dom, DomContents, styled} from 'grainjs';
|
import {Computed, dom, DomContents, styled} from 'grainjs';
|
||||||
@ -90,7 +90,7 @@ function makeTeamSiteIntro(homeModel: HomeModel) {
|
|||||||
testId('welcome-title')
|
testId('welcome-title')
|
||||||
),
|
),
|
||||||
cssIntroLine(t("Get started by inviting your team and creating your first Grist document.")),
|
cssIntroLine(t("Get started by inviting your team and creating your first Grist document.")),
|
||||||
(shouldHideUiElement('helpCenter') ? null :
|
(!isFeatureEnabled('helpCenter') ? null :
|
||||||
cssIntroLine(
|
cssIntroLine(
|
||||||
'Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.', // TODO i18n
|
'Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.', // TODO i18n
|
||||||
testId('welcome-text')
|
testId('welcome-text')
|
||||||
@ -104,7 +104,7 @@ function makePersonalIntro(homeModel: HomeModel, user: FullUser) {
|
|||||||
return [
|
return [
|
||||||
css.docListHeader(t("Welcome to Grist, {{- name}}!", {name: user.name}), testId('welcome-title')),
|
css.docListHeader(t("Welcome to Grist, {{- name}}!", {name: user.name}), testId('welcome-title')),
|
||||||
cssIntroLine(t("Get started by creating your first Grist document.")),
|
cssIntroLine(t("Get started by creating your first Grist document.")),
|
||||||
(shouldHideUiElement('helpCenter') ? null :
|
(!isFeatureEnabled('helpCenter') ? null :
|
||||||
cssIntroLine(t("Visit our {{link}} to learn more.", { link: helpCenterLink() }),
|
cssIntroLine(t("Visit our {{link}} to learn more.", { link: helpCenterLink() }),
|
||||||
testId('welcome-text'))
|
testId('welcome-text'))
|
||||||
),
|
),
|
||||||
@ -118,7 +118,7 @@ function makeAnonIntro(homeModel: HomeModel) {
|
|||||||
css.docListHeader(t("Welcome to Grist!"), testId('welcome-title')),
|
css.docListHeader(t("Welcome to Grist!"), testId('welcome-title')),
|
||||||
cssIntroLine(t("Get started by exploring templates, or creating your first Grist document.")),
|
cssIntroLine(t("Get started by exploring templates, or creating your first Grist document.")),
|
||||||
cssIntroLine(t("{{signUp}} to save your work. ", {signUp}),
|
cssIntroLine(t("{{signUp}} to save your work. ", {signUp}),
|
||||||
(shouldHideUiElement('helpCenter') ? null : t("Visit our {{link}} to learn more.", { link: helpCenterLink() })),
|
(!isFeatureEnabled('helpCenter') ? null : t("Visit our {{link}} to learn more.", { link: helpCenterLink() })),
|
||||||
testId('welcome-text')),
|
testId('welcome-text')),
|
||||||
makeCreateButtons(homeModel),
|
makeCreateButtons(homeModel),
|
||||||
];
|
];
|
||||||
@ -143,7 +143,7 @@ function buildButtons(homeModel: HomeModel, options: {
|
|||||||
!options.templates ? null :
|
!options.templates ? null :
|
||||||
cssBtn(cssBtnIcon('FieldTable'), t("Browse Templates"), testId('intro-templates'),
|
cssBtn(cssBtnIcon('FieldTable'), t("Browse Templates"), testId('intro-templates'),
|
||||||
cssButton.cls('-primary'),
|
cssButton.cls('-primary'),
|
||||||
dom.hide(shouldHideUiElement("templates")),
|
dom.show(isFeatureEnabled("templates")),
|
||||||
urlState().setLinkUrl({homePage: 'templates'}),
|
urlState().setLinkUrl({homePage: 'templates'}),
|
||||||
),
|
),
|
||||||
!options.import ? null :
|
!options.import ? null :
|
||||||
|
@ -16,7 +16,7 @@ import {testId, theme} from 'app/client/ui2018/cssVars';
|
|||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {menu, menuIcon, menuItem, upgradableMenuItem, upgradeText} from 'app/client/ui2018/menus';
|
import {menu, menuIcon, menuItem, upgradableMenuItem, upgradeText} from 'app/client/ui2018/menus';
|
||||||
import {confirmModal} from 'app/client/ui2018/modals';
|
import {confirmModal} from 'app/client/ui2018/modals';
|
||||||
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
|
import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {Workspace} from 'app/common/UserAPI';
|
import {Workspace} from 'app/common/UserAPI';
|
||||||
import {computed, dom, domComputed, DomElementArg, observable, Observable, styled} from 'grainjs';
|
import {computed, dom, domComputed, DomElementArg, observable, Observable, styled} from 'grainjs';
|
||||||
@ -109,7 +109,7 @@ export function createHomeLeftPane(leftPanelOpen: Observable<boolean>, home: Hom
|
|||||||
)),
|
)),
|
||||||
cssTools(
|
cssTools(
|
||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
dom.hide(shouldHideUiElement("templates")),
|
dom.show(isFeatureEnabled("templates")),
|
||||||
cssPageEntry.cls('-selected', (use) => use(home.currentPage) === "templates"),
|
cssPageEntry.cls('-selected', (use) => use(home.currentPage) === "templates"),
|
||||||
cssPageLink(cssPageIcon('Board'), cssLinkText(t("Examples & Templates")),
|
cssPageLink(cssPageIcon('Board'), cssLinkText(t("Examples & Templates")),
|
||||||
urlState().setLinkUrl({homePage: "templates"}),
|
urlState().setLinkUrl({homePage: "templates"}),
|
||||||
@ -125,7 +125,7 @@ export function createHomeLeftPane(leftPanelOpen: Observable<boolean>, home: Hom
|
|||||||
),
|
),
|
||||||
cssSpacer(),
|
cssSpacer(),
|
||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
dom.hide(shouldHideUiElement("templates")),
|
dom.show(isFeatureEnabled('tutorials')),
|
||||||
cssPageLink(cssPageIcon('Bookmark'), cssLinkText(t("Tutorial")),
|
cssPageLink(cssPageIcon('Bookmark'), cssLinkText(t("Tutorial")),
|
||||||
{ href: commonUrls.basicTutorial, target: '_blank' },
|
{ href: commonUrls.basicTutorial, target: '_blank' },
|
||||||
testId('dm-basic-tutorial'),
|
testId('dm-basic-tutorial'),
|
||||||
|
@ -18,7 +18,7 @@ import {makeT} from 'app/client/lib/localization';
|
|||||||
import {AppModel} from 'app/client/models/AppModel';
|
import {AppModel} from 'app/client/models/AppModel';
|
||||||
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
|
import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {dom, DomContents, Observable, styled} from 'grainjs';
|
import {dom, DomContents, Observable, styled} from 'grainjs';
|
||||||
|
|
||||||
const t = makeT('LeftPanelCommon');
|
const t = makeT('LeftPanelCommon');
|
||||||
@ -28,7 +28,7 @@ const t = makeT('LeftPanelCommon');
|
|||||||
* HelpCenter in a new tab.
|
* HelpCenter in a new tab.
|
||||||
*/
|
*/
|
||||||
export function createHelpTools(appModel: AppModel): DomContents {
|
export function createHelpTools(appModel: AppModel): DomContents {
|
||||||
if (shouldHideUiElement("helpCenter")) {
|
if (!isFeatureEnabled("helpCenter")) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return cssSplitPageEntry(
|
return cssSplitPageEntry(
|
||||||
|
@ -10,7 +10,7 @@ import {theme, vars} from 'app/client/ui2018/cssVars';
|
|||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {IconName} from "app/client/ui2018/IconList";
|
import {IconName} from "app/client/ui2018/IconList";
|
||||||
import {menuCssClass} from 'app/client/ui2018/menus';
|
import {menuCssClass} from 'app/client/ui2018/menus';
|
||||||
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
|
import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {dom, makeTestId, styled} from 'grainjs';
|
import {dom, makeTestId, styled} from 'grainjs';
|
||||||
import {cssMenu, defaultMenuOptions, IOpenController, setPopupToCreateDom} from 'popweasel';
|
import {cssMenu, defaultMenuOptions, IOpenController, setPopupToCreateDom} from 'popweasel';
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ function buildNotifyDropdown(ctl: IOpenController, notifier: Notifier, appModel:
|
|||||||
cssDropdownContent(
|
cssDropdownContent(
|
||||||
cssDropdownHeader(
|
cssDropdownHeader(
|
||||||
cssDropdownHeaderTitle(t("Notifications")),
|
cssDropdownHeaderTitle(t("Notifications")),
|
||||||
shouldHideUiElement("helpCenter") ? null :
|
!isFeatureEnabled("helpCenter") ? null :
|
||||||
cssDropdownFeedbackLink(
|
cssDropdownFeedbackLink(
|
||||||
cssDropdownFeedbackIcon('Feedback'),
|
cssDropdownFeedbackIcon('Feedback'),
|
||||||
t("Give feedback"),
|
t("Give feedback"),
|
||||||
|
@ -7,7 +7,7 @@ import {YouTubePlayer} from 'app/client/ui/YouTubePlayer';
|
|||||||
import {theme} from 'app/client/ui2018/cssVars';
|
import {theme} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {cssModalCloseButton, modal} from 'app/client/ui2018/modals';
|
import {cssModalCloseButton, modal} from 'app/client/ui2018/modals';
|
||||||
import {shouldHideUiElement} from 'app/common/gristUrls';
|
import {isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {dom, makeTestId, styled} from 'grainjs';
|
import {dom, makeTestId, styled} from 'grainjs';
|
||||||
|
|
||||||
const t = makeT('OpenVideoTour');
|
const t = makeT('OpenVideoTour');
|
||||||
@ -79,7 +79,7 @@ export function createVideoTourTextButton(): HTMLDivElement {
|
|||||||
* Shows the video tour on click.
|
* Shows the video tour on click.
|
||||||
*/
|
*/
|
||||||
export function createVideoTourToolsButton(): HTMLDivElement | null {
|
export function createVideoTourToolsButton(): HTMLDivElement | null {
|
||||||
if (shouldHideUiElement('helpCenter')) { return null; }
|
if (!isFeatureEnabled('helpCenter')) { return null; }
|
||||||
|
|
||||||
let iconElement: HTMLElement;
|
let iconElement: HTMLElement;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {primaryButton} from 'app/client/ui2018/buttons';
|
|||||||
import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {menu, menuAnnotate, menuDivider, menuIcon, menuItem, menuItemLink, menuText} from 'app/client/ui2018/menus';
|
import {menu, menuAnnotate, menuDivider, menuIcon, menuItem, menuItemLink, menuText} from 'app/client/ui2018/menus';
|
||||||
import {buildUrlId, parseUrlId, shouldHideUiElement} from 'app/common/gristUrls';
|
import {buildUrlId, isFeatureEnabled, parseUrlId} from 'app/common/gristUrls';
|
||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
import {Document} from 'app/common/UserAPI';
|
import {Document} from 'app/common/UserAPI';
|
||||||
import {dom, DomContents, styled} from 'grainjs';
|
import {dom, DomContents, styled} from 'grainjs';
|
||||||
@ -244,7 +244,7 @@ function menuExports(doc: Document, pageModel: DocPageModel) {
|
|||||||
href: pageModel.appModel.api.getDocAPI(doc.id).getDownloadXlsxUrl(),
|
href: pageModel.appModel.api.getDocAPI(doc.id).getDownloadXlsxUrl(),
|
||||||
target: '_blank', download: ''
|
target: '_blank', download: ''
|
||||||
}, menuIcon('Download'), t("Export XLSX"), testId('tb-share-option')),
|
}, menuIcon('Download'), t("Export XLSX"), testId('tb-share-option')),
|
||||||
(shouldHideUiElement("sendToDrive") ? null : menuItem(() => sendToDrive(doc, pageModel),
|
(!isFeatureEnabled("sendToDrive") ? null : menuItem(() => sendToDrive(doc, pageModel),
|
||||||
menuIcon('Download'), t("Send to Google Drive"), testId('tb-share-option'))),
|
menuIcon('Download'), t("Send to Google Drive"), testId('tb-share-option'))),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {dom, makeTestId, styled} from 'grainjs';
|
import {dom, makeTestId, styled} from 'grainjs';
|
||||||
import {getSingleOrg, shouldHideUiElement} from 'app/common/gristUrls';
|
import {getSingleOrg, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {getOrgName} from 'app/common/UserAPI';
|
import {getOrgName} from 'app/common/UserAPI';
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {AppModel} from 'app/client/models/AppModel';
|
import {AppModel} from 'app/client/models/AppModel';
|
||||||
@ -17,7 +17,7 @@ const testId = makeTestId('test-site-switcher-');
|
|||||||
*/
|
*/
|
||||||
export function maybeAddSiteSwitcherSection(appModel: AppModel) {
|
export function maybeAddSiteSwitcherSection(appModel: AppModel) {
|
||||||
const orgs = appModel.topAppModel.orgs;
|
const orgs = appModel.topAppModel.orgs;
|
||||||
return dom.maybe((use) => use(orgs).length > 0 && !getSingleOrg() && !shouldHideUiElement("multiSite"), () => [
|
return dom.maybe((use) => use(orgs).length > 0 && !getSingleOrg() && isFeatureEnabled("multiSite"), () => [
|
||||||
menuDivider(),
|
menuDivider(),
|
||||||
buildSiteSwitcher(appModel),
|
buildSiteSwitcher(appModel),
|
||||||
]);
|
]);
|
||||||
|
@ -2,7 +2,7 @@ import {AppModel} from 'app/client/models/AppModel';
|
|||||||
import {bigPrimaryButton} from 'app/client/ui2018/buttons';
|
import {bigPrimaryButton} from 'app/client/ui2018/buttons';
|
||||||
import {isNarrowScreenObs, theme} from 'app/client/ui2018/cssVars';
|
import {isNarrowScreenObs, theme} from 'app/client/ui2018/cssVars';
|
||||||
import {icon} from 'app/client/ui2018/icons';
|
import {icon} from 'app/client/ui2018/icons';
|
||||||
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
|
import {commonUrls, isFeatureEnabled} from 'app/common/gristUrls';
|
||||||
import {Computed, dom, IDisposableOwner, makeTestId, styled} from 'grainjs';
|
import {Computed, dom, IDisposableOwner, makeTestId, styled} from 'grainjs';
|
||||||
|
|
||||||
const testId = makeTestId('test-tutorial-card-');
|
const testId = makeTestId('test-tutorial-card-');
|
||||||
@ -12,7 +12,7 @@ interface Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildTutorialCard(owner: IDisposableOwner, options: Options) {
|
export function buildTutorialCard(owner: IDisposableOwner, options: Options) {
|
||||||
if (shouldHideUiElement('templates')) { return null; }
|
if (!isFeatureEnabled('tutorials')) { return null; }
|
||||||
|
|
||||||
const {app} = options;
|
const {app} = options;
|
||||||
const dismissed = app.dismissedPopup('tutorialFirstCard');
|
const dismissed = app.dismissedPopup('tutorialFirstCard');
|
||||||
|
@ -584,8 +584,8 @@ export interface GristLoadConfig {
|
|||||||
|
|
||||||
activation?: Activation;
|
activation?: Activation;
|
||||||
|
|
||||||
// Parts of the UI to hide
|
// List of enabled features.
|
||||||
hideUiElements?: IHideableUiElement[];
|
features?: IFeature[];
|
||||||
|
|
||||||
// String to append to the end of the HTML document.title
|
// String to append to the end of the HTML document.title
|
||||||
pageTitleSuffix?: string;
|
pageTitleSuffix?: string;
|
||||||
@ -612,12 +612,19 @@ export interface GristLoadConfig {
|
|||||||
userLocale?: string;
|
userLocale?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HideableUiElements = StringUnion("helpCenter", "billing", "templates", "multiSite", "multiAccounts",
|
export const Features = StringUnion(
|
||||||
"sendToDrive");
|
"helpCenter",
|
||||||
export type IHideableUiElement = typeof HideableUiElements.type;
|
"billing",
|
||||||
|
"templates",
|
||||||
|
"multiSite",
|
||||||
|
"multiAccounts",
|
||||||
|
"sendToDrive",
|
||||||
|
"tutorials",
|
||||||
|
);
|
||||||
|
export type IFeature = typeof Features.type;
|
||||||
|
|
||||||
export function shouldHideUiElement(elem: IHideableUiElement): boolean {
|
export function isFeatureEnabled(feature: IFeature): boolean {
|
||||||
return (getGristConfig().hideUiElements || []).includes(elem);
|
return (getGristConfig().features || []).includes(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPageTitleSuffix(config?: GristLoadConfig) {
|
export function getPageTitleSuffix(config?: GristLoadConfig) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {getPageTitleSuffix, GristLoadConfig, HideableUiElements, IHideableUiElement} from 'app/common/gristUrls';
|
import {Features, getPageTitleSuffix, GristLoadConfig, IFeature} from 'app/common/gristUrls';
|
||||||
import {isAffirmative} from 'app/common/gutil';
|
import {isAffirmative} from 'app/common/gutil';
|
||||||
import {getTagManagerSnippet} from 'app/common/tagManager';
|
import {getTagManagerSnippet} from 'app/common/tagManager';
|
||||||
import {Document} from 'app/common/UserAPI';
|
import {Document} from 'app/common/UserAPI';
|
||||||
@ -13,6 +13,7 @@ import * as fse from 'fs-extra';
|
|||||||
import jsesc from 'jsesc';
|
import jsesc from 'jsesc';
|
||||||
import * as handlebars from 'handlebars';
|
import * as handlebars from 'handlebars';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import difference = require('lodash/difference');
|
||||||
|
|
||||||
const translate = (req: express.Request, key: string, args?: any) => req.t(`sendAppPage.${key}`, args);
|
const translate = (req: express.Request, key: string, args?: any) => req.t(`sendAppPage.${key}`, args);
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ export function makeGristConfig(homeUrl: string|null, extra: Partial<GristLoadCo
|
|||||||
pathOnly,
|
pathOnly,
|
||||||
supportAnon: shouldSupportAnon(),
|
supportAnon: shouldSupportAnon(),
|
||||||
supportEngines: getSupportedEngineChoices(),
|
supportEngines: getSupportedEngineChoices(),
|
||||||
hideUiElements: getHiddenUiElements(),
|
features: getFeatures(),
|
||||||
pageTitleSuffix: configuredPageTitleSuffix(),
|
pageTitleSuffix: configuredPageTitleSuffix(),
|
||||||
pluginUrl,
|
pluginUrl,
|
||||||
stripeAPIKey: process.env.STRIPE_PUBLIC_API_KEY,
|
stripeAPIKey: process.env.STRIPE_PUBLIC_API_KEY,
|
||||||
@ -143,12 +144,10 @@ function shouldSupportAnon() {
|
|||||||
return process.env.GRIST_SUPPORT_ANON === "true";
|
return process.env.GRIST_SUPPORT_ANON === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHiddenUiElements(): IHideableUiElement[] {
|
function getFeatures(): IFeature[] {
|
||||||
const str = process.env.GRIST_HIDE_UI_ELEMENTS;
|
const disabledFeatures = process.env.GRIST_HIDE_UI_ELEMENTS?.split(',') ?? [];
|
||||||
if (!str) {
|
const enabledFeatures = process.env.GRIST_UI_FEATURES?.split(',') ?? Features.values;
|
||||||
return [];
|
return Features.checkAll(difference(enabledFeatures, disabledFeatures));
|
||||||
}
|
|
||||||
return HideableUiElements.checkAll(str.split(","));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function configuredPageTitleSuffix() {
|
function configuredPageTitleSuffix() {
|
||||||
|
@ -30,6 +30,8 @@ if (!process.env.GRIST_SINGLE_ORG) {
|
|||||||
setDefaultEnv('GRIST_ORG_IN_PATH', 'true');
|
setDefaultEnv('GRIST_ORG_IN_PATH', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDefaultEnv('GRIST_UI_FEATURES', 'helpCenter,billing,templates,multiSite,multiAccounts,sendToDrive');
|
||||||
|
|
||||||
import {updateDb} from 'app/server/lib/dbUtils';
|
import {updateDb} from 'app/server/lib/dbUtils';
|
||||||
import {main as mergedServerMain} from 'app/server/mergedServerMain';
|
import {main as mergedServerMain} from 'app/server/mergedServerMain';
|
||||||
import * as fse from 'fs-extra';
|
import * as fse from 'fs-extra';
|
||||||
|
51
test/nbrowser/Features.ts
Normal file
51
test/nbrowser/Features.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { assert, driver } from 'mocha-webdriver';
|
||||||
|
import * as gu from 'test/nbrowser/gristUtils';
|
||||||
|
import { server, setupTestSuite } from 'test/nbrowser/testUtils';
|
||||||
|
import { EnvironmentSnapshot } from 'test/server/testUtils';
|
||||||
|
|
||||||
|
describe('Features', function () {
|
||||||
|
this.timeout(20000);
|
||||||
|
setupTestSuite();
|
||||||
|
|
||||||
|
let session: gu.Session;
|
||||||
|
let oldEnv: EnvironmentSnapshot;
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
oldEnv = new EnvironmentSnapshot();
|
||||||
|
session = await gu.session().teamSite.login();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
oldEnv.restore();
|
||||||
|
await server.restart();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be enabled with the GRIST_UI_FEATURES env variable', async function () {
|
||||||
|
process.env.GRIST_UI_FEATURES = 'helpCenter,templates';
|
||||||
|
await server.restart();
|
||||||
|
await session.loadDocMenu('/');
|
||||||
|
assert.isTrue(await driver.find('.test-dm-templates-page').isDisplayed());
|
||||||
|
assert.isTrue(await driver.find('.test-left-feedback').isDisplayed());
|
||||||
|
assert.isFalse(await driver.find('.test-dm-basic-tutorial').isDisplayed());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can be disabled with the GRIST_HIDE_UI_ELEMENTS env variable', async function () {
|
||||||
|
delete process.env.GRIST_UI_FEATURES;
|
||||||
|
process.env.GRIST_HIDE_UI_ELEMENTS = 'templates';
|
||||||
|
await server.restart();
|
||||||
|
await session.loadDocMenu('/');
|
||||||
|
assert.isTrue(await driver.find('.test-left-feedback').isDisplayed());
|
||||||
|
assert.isTrue(await driver.find('.test-dm-basic-tutorial').isDisplayed());
|
||||||
|
assert.isFalse(await driver.find('.test-dm-templates-page').isDisplayed());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('that are disabled take precedence if also enabled', async function () {
|
||||||
|
process.env.GRIST_UI_FEATURES = 'tutorials,templates';
|
||||||
|
process.env.GRIST_HIDE_UI_ELEMENTS = 'helpCenter,templates';
|
||||||
|
await server.restart();
|
||||||
|
await session.loadDocMenu('/');
|
||||||
|
assert.isTrue(await driver.find('.test-dm-basic-tutorial').isDisplayed());
|
||||||
|
assert.isFalse(await driver.find('.test-left-feedback').isPresent());
|
||||||
|
assert.isFalse(await driver.find('.test-dm-templates-page').isDisplayed());
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user