Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

Commit

Permalink
Fix Bug 1502501 - Conditionally show StartupOverlay for FxA sign-in …
Browse files Browse the repository at this point in the history
…on about:welcome (#4558)
  • Loading branch information
sarracini committed Nov 15, 2018
1 parent 4cec5cd commit 7bc6efe
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 46 deletions.
3 changes: 1 addition & 2 deletions content-src/activity-stream.jsx
Expand Up @@ -21,14 +21,13 @@ new DetectUserSessionStart(store).sendEventOrAddListener();
if (!global.gActivityStreamPrerenderedState) {
store.dispatch(ac.AlsoToMain({type: at.NEW_TAB_STATE_REQUEST}));
}
enableASRouterContent(store, asrouterContent);

ReactDOM.hydrate(<Provider store={store}>
<Base
isFirstrun={global.document.location.href === "about:welcome"}
isPrerendered={!!global.gActivityStreamPrerenderedState}
locale={global.document.documentElement.lang}
strings={global.gActivityStreamStrings} />
</Provider>, document.getElementById("root"));

enableASRouterContent(store, asrouterContent);
addSnippetsSubscriber(store);
58 changes: 48 additions & 10 deletions content-src/asrouter/asrouter-content.jsx
@@ -1,3 +1,4 @@
import {addLocaleData, IntlProvider} from "react-intl";
import {actionCreators as ac} from "common/Actions.jsm";
import {OUTGOING_MESSAGE_NAME as AS_GENERAL_OUTGOING_MESSAGE_NAME} from "content-src/lib/init-store";
import {generateMessages} from "./rich-text-strings";
Expand All @@ -7,6 +8,7 @@ import {OnboardingMessage} from "./templates/OnboardingMessage/OnboardingMessage
import React from "react";
import ReactDOM from "react-dom";
import {SnippetsTemplates} from "./templates/template-manifest";
import {StartupOverlay} from "./templates/StartupOverlay/StartupOverlay";

const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
Expand Down Expand Up @@ -170,6 +172,9 @@ export class ASRouterUISurface extends React.PureComponent {
}

componentWillMount() {
// Add locale data for StartupOverlay because it uses react-intl
addLocaleData(global.document.documentElement.lang);

const endpoint = ASRouterUtils.getPreviewEndpoint();
ASRouterUtils.addListener(this.onMessageFromParent);

Expand All @@ -181,11 +186,23 @@ export class ASRouterUISurface extends React.PureComponent {
}
}

componentDidMount() {
if (this.props.document.location.href === "about:welcome") {
// Trigger the onboarding overlay slightly after the startup overlay is mounted,
// to ensure we don't flash the onboarding content before hand
ASRouterUtils.sendMessage({type: "TRIGGER", data: {trigger: {id: "showOnboarding"}}});
}
}

componentWillUnmount() {
ASRouterUtils.removeListener(this.onMessageFromParent);
}

renderSnippets() {
if (this.state.bundle.template === "onboarding" ||
this.state.message.template === "fxa_overlay") {
return null;
}
const SnippetComponent = SnippetsTemplates[this.state.message.template];
const {content} = this.state.message;

Expand All @@ -211,13 +228,31 @@ export class ASRouterUISurface extends React.PureComponent {
}

renderOnboarding() {
return (
<OnboardingMessage
{...this.state.bundle}
UISurface="NEWTAB_OVERLAY"
onAction={ASRouterUtils.executeAction}
onDoneButton={this.clearBundle(this.state.bundle.bundle)}
sendUserActionTelemetry={this.sendUserActionTelemetry} />);
if (this.state.bundle.template === "onboarding") {
return (
<OnboardingMessage
{...this.state.bundle}
UISurface="NEWTAB_OVERLAY"
onAction={ASRouterUtils.executeAction}
onDoneButton={this.clearBundle(this.state.bundle.bundle)}
sendUserActionTelemetry={this.sendUserActionTelemetry} />);
}
return null;
}

renderFirstRunOverlay() {
if (this.state.message.template === "fxa_overlay") {
global.document.body.classList.add("welcome", "hide-main");
return (
<IntlProvider locale={global.document.documentElement.lang} messages={global.gActivityStreamStrings}>
<StartupOverlay
onBlock={this.onBlockById(this.state.message.id)}
dispatch={this.props.activityStreamStore.dispatch}
store={this.props.activityStreamStore} />
</IntlProvider>
);
}
return null;
}

renderPreviewBanner() {
Expand All @@ -239,7 +274,9 @@ export class ASRouterUISurface extends React.PureComponent {
return (
<React.Fragment>
{this.renderPreviewBanner()}
{bundle.template === "onboarding" ? this.renderOnboarding() : this.renderSnippets()}
{this.renderFirstRunOverlay()}
{this.renderOnboarding()}
{this.renderSnippets()}
</React.Fragment>
);
}
Expand All @@ -262,14 +299,15 @@ export class ASRouterContent {
global.document.body.appendChild(this.containerElement);
}

ReactDOM.render(<ASRouterUISurface />, this.containerElement);
ReactDOM.render(<ASRouterUISurface activityStreamStore={this._activityStreamStore} />, this.containerElement);
}

_unmount() {
ReactDOM.unmountComponentAtNode(this.containerElement);
}

init() {
init(store) {
this._activityStreamStore = store;
this._mount();
this.initialized = true;
}
Expand Down
Expand Up @@ -56,6 +56,7 @@ export class _StartupOverlay extends React.PureComponent {
window.removeEventListener("visibilitychange", this.removeOverlay);
document.body.classList.remove("hide-main");
this.setState({show: false});
this.props.onBlock();
setTimeout(() => {
// Allow scrolling and fully remove overlay after animation finishes.
document.body.classList.remove("welcome");
Expand Down
5 changes: 0 additions & 5 deletions content-src/components/Base/Base.jsx
Expand Up @@ -9,7 +9,6 @@ import {PrerenderData} from "common/PrerenderData.jsm";
import React from "react";
import {Search} from "content-src/components/Search/Search";
import {Sections} from "content-src/components/Sections/Sections";
import {StartupOverlay} from "content-src/components/StartupOverlay/StartupOverlay";

const PrefsButton = injectIntl(props => (
<div className="prefs-button">
Expand Down Expand Up @@ -42,9 +41,6 @@ export class _Base extends React.PureComponent {
componentWillMount() {
const {locale} = this.props;
addLocaleDataForReactIntl(locale);
if (this.props.isFirstrun) {
global.document.body.classList.add("welcome", "hide-main");
}
}

componentDidMount() {
Expand Down Expand Up @@ -170,7 +166,6 @@ export class BaseContent extends React.PureComponent {
<ConfirmDialog />
</main>
</div>
{this.props.isFirstrun && <StartupOverlay />}
</div>);
}
}
Expand Down
2 changes: 1 addition & 1 deletion content-src/lib/asroutercontent.js
Expand Up @@ -7,7 +7,7 @@ export function enableASRouterContent(store, asrouterContent) {
}

if (!asrouterContent.initialized) {
asrouterContent.init();
asrouterContent.init(store);
}
});
// Return this for testing purposes
Expand Down
2 changes: 1 addition & 1 deletion content-src/styles/_activity-stream.scss
Expand Up @@ -133,7 +133,6 @@ input {
@import '../components/ErrorBoundary/ErrorBoundary';
@import '../components/TopSites/TopSites';
@import '../components/Sections/Sections';
@import '../components/StartupOverlay/StartupOverlay';
@import '../components/Topics/Topics';
@import '../components/Search/Search';
@import '../components/ContextMenu/ContextMenu';
Expand All @@ -153,3 +152,4 @@ input {
@import '../asrouter/templates/SubmitFormSnippet/SubmitFormSnippet';
@import '../asrouter/templates/OnboardingMessage/OnboardingMessage';
@import '../asrouter/templates/EOYSnippet/EOYSnippet';
@import '../asrouter/templates/StartupOverlay/StartupOverlay';
19 changes: 15 additions & 4 deletions lib/OnboardingMessageProvider.jsm
Expand Up @@ -24,7 +24,7 @@ const ONBOARDING_MESSAGES = async () => ([
button_label: {string_id: "onboarding-button-label-try-now"},
button_action: {type: "OPEN_PRIVATE_BROWSER_WINDOW"},
},
trigger: {id: "firstRun"},
trigger: {id: "showOnboarding"},
},
{
id: "ONBOARDING_2",
Expand All @@ -41,7 +41,7 @@ const ONBOARDING_MESSAGES = async () => ([
data: {args: "https://screenshots.firefox.com/#tour", where: "tabshifted"},
},
},
trigger: {id: "firstRun"},
trigger: {id: "showOnboarding"},
},
{
id: "ONBOARDING_3",
Expand All @@ -59,7 +59,7 @@ const ONBOARDING_MESSAGES = async () => ([
},
},
targeting: "attributionData.campaign != 'non-fx-button' && attributionData.source != 'addons.mozilla.org'",
trigger: {id: "firstRun"},
trigger: {id: "showOnboarding"},
},
{
id: "ONBOARDING_4",
Expand All @@ -77,7 +77,7 @@ const ONBOARDING_MESSAGES = async () => ([
},
},
targeting: "providerCohorts.onboarding == 'ghostery'",
trigger: {id: "firstRun"},
trigger: {id: "showOnboarding"},
},
{
id: "ONBOARDING_5",
Expand All @@ -95,6 +95,12 @@ const ONBOARDING_MESSAGES = async () => ([
},
},
targeting: "attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'",
trigger: {id: "showOnboarding"},
},
{
id: "FXA_1",
template: "fxa_overlay",
targeting: "attributionData.campaign != 'non-fx-button' && attributionData.source != 'addons.mozilla.org'",
trigger: {id: "firstRun"},
},
]);
Expand All @@ -120,6 +126,11 @@ const OnboardingMessageProvider = {
let translatedMessages = [];
for (const msg of messages) {
let translatedMessage = msg;
// If the message has no content, do not attempt to translate it
if (!msg.content) {
translatedMessages.push(msg);
continue;
}
const [button_string, title_string, text_string] = await L10N.formatMessages([
{id: msg.content.button_label.string_id},
{id: msg.content.title.string_id},
Expand Down
2 changes: 1 addition & 1 deletion test/unit/activity-stream.test.jsx
Expand Up @@ -26,7 +26,7 @@ describe("asrouter", () => {
// Dispatch another irrelevant event to make sure we don't initialize twice.
store.dispatch({type: at.PREF_CHANGED, data: {name: "foo", value: "bar"}});

assert.calledOnce(asrouterContent.init);
assert.calledWith(asrouterContent.init, store);
});
it("should do nothing if ASRouter is not initialized", () => {
const addStub = sandbox.stub(global.document.body.classList, "add");
Expand Down
3 changes: 2 additions & 1 deletion test/unit/asrouter/templates/OnboardingMessage.test.jsx
Expand Up @@ -41,6 +41,7 @@ describe("OnboardingMessage", () => {
});
it("should validate all messages from OnboardingMessageProvider", async () => {
const messages = await OnboardingMessageProvider.getUntranslatedMessages();
messages.forEach(msg => assert.jsonSchema(msg.content, schema));
// FXA_1 doesn't have content - so filter it out
messages.filter(msg => msg.content).forEach(msg => assert.jsonSchema(msg.content, schema));
});
});
19 changes: 0 additions & 19 deletions test/unit/content-src/components/Base.test.jsx
Expand Up @@ -3,7 +3,6 @@ import {ErrorBoundary} from "content-src/components/ErrorBoundary/ErrorBoundary"
import React from "react";
import {Search} from "content-src/components/Search/Search";
import {shallow} from "enzyme";
import {StartupOverlay} from "content-src/components/StartupOverlay/StartupOverlay";

describe("<Base>", () => {
let DEFAULT_PROPS = {store: {getState: () => {}}, App: {initialized: true}, Prefs: {values: {}}, Sections: [], dispatch: () => {}};
Expand Down Expand Up @@ -38,22 +37,4 @@ describe("<BaseContent>", () => {

assert.isTrue(wrapper.find(Search).parent().is(ErrorBoundary));
});

it("should render a StartupOverlay when on about:welcome (props are sent as true)", () => {
const isFirstrunProps =
Object.assign({}, DEFAULT_PROPS, {isFirstrun: true});

const wrapper = shallow(<BaseContent {...isFirstrunProps} />);

assert.ok(wrapper.find(StartupOverlay).exists());
});

it("should not render a StartupOverlay when not on about:welcome (props are sent as false)", () => {
const notFirstrunProps =
Object.assign({}, DEFAULT_PROPS, {isFirstrun: false});

const wrapper = shallow(<BaseContent {...notFirstrunProps} />);

assert.ok(!wrapper.find(StartupOverlay).exists());
});
});
9 changes: 7 additions & 2 deletions test/unit/content-src/components/StartupOverlay.test.jsx
@@ -1,14 +1,19 @@
import {actionCreators as ac, actionTypes as at} from "common/Actions.jsm";
import {mountWithIntl} from "test/unit/utils";
import React from "react";
import {_StartupOverlay as StartupOverlay} from "content-src/components/StartupOverlay/StartupOverlay";
import {_StartupOverlay as StartupOverlay} from "content-src/asrouter/templates/StartupOverlay/StartupOverlay";

describe("<StartupOverlay>", () => {
let wrapper;
let dispatch;
let onReady;
let onBlock;
beforeEach(() => {
dispatch = sinon.stub();
wrapper = mountWithIntl(<StartupOverlay dispatch={dispatch} />);
onReady = sinon.stub();
onBlock = sinon.stub();

wrapper = mountWithIntl(<StartupOverlay onBlock={onBlock} onReady={onReady} dispatch={dispatch} />);
});

it("should not render if state.show is false", () => {
Expand Down

0 comments on commit 7bc6efe

Please sign in to comment.