Bug 1592820 - Dynamic triplets click of card should replace card on refresh r=k88hudson

Differential Revision: https://phabricator.services.mozilla.com/D52610

--HG--
extra : source : 0889c2f4171cc7c108c94243a944428d2e0e17a9
This commit is contained in:
Punam Dahiya 2019-11-19 17:09:29 +00:00
Родитель 2003c127ad
Коммит c828174439
10 изменённых файлов: 225 добавлений и 44 удалений

Просмотреть файл

@ -205,6 +205,7 @@ export class FirstRun extends React.PureComponent {
flowParams={flowParams}
onDismiss={this.closeInterrupt}
fxaEndpoint={fxaEndpoint}
onBlockById={props.onBlockById}
/>
) : null}
{hasTriplets ? (
@ -219,6 +220,7 @@ export class FirstRun extends React.PureComponent {
UTMTerm={`${UTMTerm}-card`}
flowParams={flowParams}
onAction={executeAction}
onBlockById={props.onBlockById}
/>
) : null}
</>

Просмотреть файл

@ -53,6 +53,7 @@ export class Interrupt extends React.PureComponent {
sendUserActionTelemetry={sendUserActionTelemetry}
UTMTerm={UTMTerm}
flowParams={flowParams}
onBlockById={this.props.onBlockById}
/>
);
case "trailhead":

Просмотреть файл

@ -21,7 +21,7 @@ export class Triplets extends React.PureComponent {
this.props.document.body.classList.remove("inline-onboarding");
}
onCardAction(action) {
onCardAction(action, message) {
let actionUpdates = {};
const { flowParams, UTMTerm } = this.props;
@ -39,6 +39,10 @@ export class Triplets extends React.PureComponent {
}
this.props.onAction({ ...action, ...actionUpdates });
// Only block if message is in dynamic triplets experiment
if (message.blockOnClick) {
this.props.onBlockById(message.id);
}
}
onHideContainer() {
@ -70,6 +74,7 @@ export class Triplets extends React.PureComponent {
{cards.map(card => (
<OnboardingCard
key={card.id}
message={card}
className="trailheadCard"
sendUserActionTelemetry={sendUserActionTelemetry}
onAction={this.onCardAction}

Просмотреть файл

@ -66,6 +66,7 @@ export const FxCards = ({ cards, onCardAction, sendUserActionTelemetry }) => (
{cards.map(card => (
<OnboardingCard
key={card.id}
message={card}
className="trailheadCard"
sendUserActionTelemetry={sendUserActionTelemetry}
onAction={onCardAction}
@ -106,7 +107,7 @@ export class FullPageInterrupt extends React.PureComponent {
document.body.classList.remove("welcome");
}
onCardAction(action) {
onCardAction(action, message) {
let actionUpdates = {};
const { flowParams, UTMTerm } = this.props;
@ -124,6 +125,10 @@ export class FullPageInterrupt extends React.PureComponent {
}
this.props.onAction({ ...action, ...actionUpdates });
// Only block if message is in dynamic triplets experiment
if (message.blockOnClick) {
this.props.onBlockById(message.id);
}
this.removeOverlay();
}

Просмотреть файл

@ -18,7 +18,7 @@ export class OnboardingCard extends React.PureComponent {
id: props.UISurface,
};
props.sendUserActionTelemetry(ping);
props.onAction(props.content.primary_button.action);
props.onAction(props.content.primary_button.action, props.message);
}
render() {

Просмотреть файл

@ -3363,6 +3363,7 @@ const FxCards = ({
sendUserActionTelemetry
}) => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_3___default.a.Fragment, null, cards.map(card => react__WEBPACK_IMPORTED_MODULE_3___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_2__["OnboardingCard"], _extends({
key: card.id,
message: card,
className: "trailheadCard",
sendUserActionTelemetry: sendUserActionTelemetry,
onAction: onCardAction,
@ -3393,7 +3394,7 @@ class FullPageInterrupt extends react__WEBPACK_IMPORTED_MODULE_3___default.a.Pur
document.body.classList.remove("welcome");
}
onCardAction(action) {
onCardAction(action, message) {
let actionUpdates = {};
const {
flowParams,
@ -3419,7 +3420,12 @@ class FullPageInterrupt extends react__WEBPACK_IMPORTED_MODULE_3___default.a.Pur
this.props.onAction({ ...action,
...actionUpdates
});
}); // Only block if message is in dynamic triplets experiment
if (message.blockOnClick) {
this.props.onBlockById(message.id);
}
this.removeOverlay();
}
@ -3507,7 +3513,7 @@ class OnboardingCard extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCo
id: props.UISurface
};
props.sendUserActionTelemetry(ping);
props.onAction(props.content.primary_button.action);
props.onAction(props.content.primary_button.action, props.message);
}
render() {
@ -3572,7 +3578,7 @@ class Triplets extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponen
this.props.document.body.classList.remove("inline-onboarding");
}
onCardAction(action) {
onCardAction(action, message) {
let actionUpdates = {};
const {
flowParams,
@ -3598,7 +3604,11 @@ class Triplets extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponen
this.props.onAction({ ...action,
...actionUpdates
});
}); // Only block if message is in dynamic triplets experiment
if (message.blockOnClick) {
this.props.onBlockById(message.id);
}
}
onHideContainer() {
@ -3635,6 +3645,7 @@ class Triplets extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponen
className: `trailheadCardGrid${showContent ? " show" : ""}`
}, cards.map(card => react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_templates_OnboardingMessage_OnboardingMessage__WEBPACK_IMPORTED_MODULE_1__["OnboardingCard"], _extends({
key: card.id,
message: card,
className: "trailheadCard",
sendUserActionTelemetry: sendUserActionTelemetry,
onAction: this.onCardAction,
@ -14650,7 +14661,8 @@ class Interrupt_Interrupt extends external_React_default.a.PureComponent {
fxaEndpoint: fxaEndpoint,
sendUserActionTelemetry: sendUserActionTelemetry,
UTMTerm: UTMTerm,
flowParams: flowParams
flowParams: flowParams,
onBlockById: this.props.onBlockById
});
case "trailhead":
@ -14868,7 +14880,8 @@ class FirstRun_FirstRun extends external_React_default.a.PureComponent {
dispatch: dispatch,
flowParams: flowParams,
onDismiss: this.closeInterrupt,
fxaEndpoint: fxaEndpoint
fxaEndpoint: fxaEndpoint,
onBlockById: props.onBlockById
}) : null, hasTriplets ? external_React_default.a.createElement(Triplets["Triplets"], {
document: props.document,
cards: triplets,
@ -14879,7 +14892,8 @@ class FirstRun_FirstRun extends external_React_default.a.PureComponent {
sendUserActionTelemetry: sendUserActionTelemetry,
UTMTerm: `${UTMTerm}-card`,
flowParams: flowParams,
onAction: executeAction
onAction: executeAction,
onBlockById: props.onBlockById
}) : null);
}

Просмотреть файл

@ -62,6 +62,7 @@ const TRAILHEAD_CONFIG = {
INTERRUPTS_EXPERIMENT_PREF: "trailhead.firstrun.interruptsExperiment",
TRIPLETS_ENROLLED_PREF: "trailhead.firstrun.tripletsEnrolled",
DEFAULT_TRIPLET: "supercharge",
DYNAMIC_TRIPLET_BUNDLE_LENGTH: 3,
BRANCHES: {
interrupts: [
["modal_control"],
@ -1214,7 +1215,7 @@ class _ASRouter {
};
}
_findAllMessages(candidateMessages, trigger) {
_findAllMessages(candidateMessages, trigger, ordered = false) {
const messages = candidateMessages.filter(m =>
this.isBelowFrequencyCaps(m)
);
@ -1225,6 +1226,7 @@ class _ASRouter {
trigger,
context,
onError: this._handleTargetingError,
ordered,
});
}
@ -1347,32 +1349,29 @@ class _ASRouter {
}
}
} else {
while (bundledMessagesOfSameTemplate.length) {
// Find a message that matches the targeting context - or break if there are no matching messages
const message = await this._findMessage(
bundledMessagesOfSameTemplate,
trigger,
true
);
if (!message) {
/* istanbul ignore next */ // Code coverage in mochitests
break;
}
// Find all messages that matches the targeting context
const allMessages = await this._findAllMessages(
bundledMessagesOfSameTemplate,
trigger,
true
);
if (allMessages && allMessages.length) {
// Retrieve enough messages needed to fill a bundle
// Only copy the content of the message (that's what the UI cares about)
// Also delete the message we picked so we don't pick it again
result.push({
content: message.content,
id: message.id,
order: message.order || 0,
});
bundledMessagesOfSameTemplate.splice(
bundledMessagesOfSameTemplate.findIndex(msg => msg.id === message.id),
1
result = result.concat(
allMessages.slice(0, bundleLength).map(message => ({
content: message.content,
id: message.id,
order: message.order || 0,
// This is used to determine whether to block when action is triggered
// Only block for dynamic triplets experiment and when there are more messages available
blockOnClick:
this.state.trailheadTriplet.startsWith("dynamic") &&
allMessages.length >
TRAILHEAD_CONFIG.DYNAMIC_TRIPLET_BUNDLE_LENGTH,
}))
);
// Stop once we have enough messages to fill a bundle
if (result.length === bundleLength) {
break;
}
}
}

Просмотреть файл

@ -1778,7 +1778,12 @@ describe("ASRouter", () => {
provider: testMessage1.provider,
bundle: [
{ content: testMessage1.content, id: testMessage1.id, order: 1 },
{ content: testMessage2.content, id: testMessage2.id, order: 2 },
{
content: testMessage2.content,
id: testMessage2.id,
order: 2,
blockOnClick: false,
},
],
};
assert.calledWith(
@ -1885,13 +1890,14 @@ describe("ASRouter", () => {
});
describe(".includeBundle", () => {
it("should send a message with .includeBundle property with specified length and template", async () => {
let msg;
beforeEach(async () => {
let messages = [
{
id: "trailhead",
template: "trailhead",
includeBundle: {
length: 2,
length: 3,
template: "foo",
trigger: { id: "foo" },
},
@ -1901,31 +1907,142 @@ describe("ASRouter", () => {
{
id: "foo2",
template: "foo",
bundled: 2,
bundled: 3,
order: 2,
trigger: { id: "foo" },
content: { title: "Foo2", body: "Foo123-2" },
},
{
id: "foo3",
template: "foo",
bundled: 2,
bundled: 3,
order: 3,
trigger: { id: "foo" },
content: { title: "Foo3", body: "Foo123-3" },
},
{
id: "foo4",
template: "foo",
bundled: 3,
order: 1,
trigger: { id: "foo" },
content: { title: "Foo4", body: "Foo123-4" },
},
{
id: "foo5",
template: "foo",
bundled: 3,
order: 4,
trigger: { id: "foo" },
content: { title: "Foo5", body: "Foo123-5" },
},
];
sandbox.stub(Router, "_findProvider").returns(null);
await Router.setState({ messages });
const msg = fakeAsyncMessage({
msg = fakeAsyncMessage({
type: "TRIGGER",
data: { trigger: { id: "firstRun" } },
});
await Router.onMessage(msg);
});
it("should send a message with .includeBundle property with specified length and template", async () => {
await Router.onMessage(msg);
const [, resp] = msg.target.sendAsyncMessage.firstCall.args;
assert.propertyVal(resp, "type", "SET_MESSAGE");
assert.isArray(resp.data.bundle, "resp.data.bundle");
assert.lengthOf(resp.data.bundle, 2, "resp.data.bundle");
assert.lengthOf(resp.data.bundle, 3, "resp.data.bundle");
});
it("should set blockOnClick property by default false on returned ordered bundle messages", async () => {
const expectedBundle = [
{
content: { title: "Foo4", body: "Foo123-4" },
id: "foo4",
order: 1,
blockOnClick: false,
},
{
content: { title: "Foo2", body: "Foo123-2" },
id: "foo2",
order: 2,
blockOnClick: false,
},
{
content: { title: "Foo3", body: "Foo123-3" },
id: "foo3",
order: 3,
blockOnClick: false,
},
];
await Router.onMessage(msg);
const [, resp] = msg.target.sendAsyncMessage.firstCall.args;
for (let i = 0; i < 3; i++) {
assert.deepEqual(resp.data.bundle[i], expectedBundle[i]);
}
});
it("should set blockOnClick property true for dynamic triplet and matching messages more than 3", async () => {
await Router.setState({ trailheadTriplet: "dynamic" });
await Router.onMessage(msg);
const [, resp] = msg.target.sendAsyncMessage.firstCall.args;
const expectedBundle = [
{
content: { title: "Foo4", body: "Foo123-4" },
id: "foo4",
order: 1,
blockOnClick: true,
},
{
content: { title: "Foo2", body: "Foo123-2" },
id: "foo2",
order: 2,
blockOnClick: true,
},
{
content: { title: "Foo3", body: "Foo123-3" },
id: "foo3",
order: 3,
blockOnClick: true,
},
];
for (let i = 0; i < 3; i++) {
assert.deepEqual(resp.data.bundle[i], expectedBundle[i]);
}
});
it("should set blockOnClick property true for triplet branch name that starts with 'dynamic' and matching messages more than 3", async () => {
await Router.setState({ trailheadTriplet: "dynamic_test" });
await Router.onMessage(msg);
const [, resp] = msg.target.sendAsyncMessage.firstCall.args;
const expectedBundle = [
{
content: { title: "Foo4", body: "Foo123-4" },
id: "foo4",
order: 1,
blockOnClick: true,
},
{
content: { title: "Foo2", body: "Foo123-2" },
id: "foo2",
order: 2,
blockOnClick: true,
},
{
content: { title: "Foo3", body: "Foo123-3" },
id: "foo3",
order: 3,
blockOnClick: true,
},
];
for (let i = 0; i < 3; i++) {
assert.deepEqual(resp.data.bundle[i], expectedBundle[i]);
}
});
});

Просмотреть файл

@ -40,6 +40,7 @@ describe("<FullPageInterrupt>", () => {
let onBlock;
let sandbox;
let onAction;
let onBlockById;
let sendTelemetryStub;
beforeEach(async () => {
@ -47,6 +48,7 @@ describe("<FullPageInterrupt>", () => {
dispatch = sandbox.stub();
onBlock = sandbox.stub();
onAction = sandbox.stub();
onBlockById = sandbox.stub();
sendTelemetryStub = sandbox.stub();
dummyNode = document.createElement("body");
@ -69,6 +71,7 @@ describe("<FullPageInterrupt>", () => {
dispatch={dispatch}
onBlock={onBlock}
onAction={onAction}
onBlockById={onBlockById}
cards={CARDS}
document={fakeDocument}
sendUserActionTelemetry={sendTelemetryStub}
@ -117,4 +120,20 @@ describe("<FullPageInterrupt>", () => {
id: "TRAILHEAD",
});
});
it("should not call blockById by default when a card button is clicked", () => {
wrapper
.find(OnboardingCard)
.find("button.onboardingButton")
.simulate("click");
assert.notCalled(onBlockById);
});
it("should call blockById when blockOnClick on message is true", () => {
CARDS[0].blockOnClick = true;
wrapper
.find(OnboardingCard)
.find("button.onboardingButton")
.simulate("click");
assert.calledOnce(onBlockById);
assert.calledWith(onBlockById, CARDS[0].id);
});
});

Просмотреть файл

@ -27,11 +27,13 @@ describe("<Triplets>", () => {
let sendTelemetryStub;
let onAction;
let onHide;
let onBlockById;
async function setup() {
sandbox = sinon.createSandbox();
sendTelemetryStub = sandbox.stub();
onAction = sandbox.stub();
onBlockById = sandbox.stub();
onHide = sandbox.stub();
wrapper = mount(
@ -42,6 +44,7 @@ describe("<Triplets>", () => {
hideContainer={onHide}
onAction={onAction}
UTMTerm="trailhead-join-card"
onBlockById={onBlockById}
sendUserActionTelemetry={sendTelemetryStub}
/>
);
@ -93,4 +96,20 @@ describe("<Triplets>", () => {
id: "TRAILHEAD",
});
});
it("should not call blockById by default when a card button is clicked", () => {
wrapper
.find(OnboardingCard)
.find("button.onboardingButton")
.simulate("click");
assert.notCalled(onBlockById);
});
it("should call blockById when blockOnClick on message is true", () => {
CARDS[0].blockOnClick = true;
wrapper
.find(OnboardingCard)
.find("button.onboardingButton")
.simulate("click");
assert.calledOnce(onBlockById);
assert.calledWith(onBlockById, CARDS[0].id);
});
});