Skip to content

Commit 8d96b5b

Browse files
jalopezmoretti
authored andcommitted
Add the possibility to show debugger in production builds (#5)
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <h1>Table of Contents</h1> - [Description](#description) - [Motivation and Context](#motivation-and-context) - [How Has This Been Tested?](#how-has-this-been-tested) - [Screenshots (if appropriate):](#screenshots-if-appropriate) - [Types of changes](#types-of-changes) - [Checklist:](#checklist) <!-- END doctoc generated TOC please keep comment here to allow auto update --> <!--- Thank you for your pull request! This is a community supported project initially released by https://github.com/wehriam for your use and enjoyment. --> <!--- Provide a general summary of your changes in the Title above --> ## Description <!--- Describe your changes in detail --> Add a new method in the debugger (`setDebuggerAvailable`) that allows a developer to override the default setting that makes the debugger unavailable in production builds ## Motivation and Context <!--- Why is this change required? What problem does it solve? --> <!--- If it fixes an open issue, please link to the issue here. --> There are some scenarios where a developer could want to have the debugger enabled in a production build, or change this behaviour at runtime (for instance, have it enabled in an integration environment but disabled in production one). Fixes: #3, pushtell#49 ## How Has This Been Tested? <!--- Please describe in detail how you tested your changes. --> <!--- Include details of your testing environment, and the tests you ran to --> <!--- see how your change affects other areas of the code, etc. --> It is a non-breaking change tested with new unit tests. ## Screenshots (if appropriate): ## Types of changes <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> <!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [X] My code follows the code style of this project. - [X] My change requires a change to the documentation. - [X] I have updated the documentation accordingly. - [X] I have added tests to cover my changes. - [X] All new and existing tests passed.
1 parent 956cee9 commit 8d96b5b

3 files changed

Lines changed: 181 additions & 149 deletions

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Please [★ on GitHub](https://github.com/marvelapp/react-ab-test)!
6161
- [`Subscription`](#subscription)
6262
- [`subscription.remove()`](#subscriptionremove)
6363
- [`experimentDebugger`](#experimentdebugger)
64+
- [`experimentDebugger.setDebuggerAvailable(isAvailable)`](#experimentdebuggersetdebuggeravailableisavailable)
6465
- [`experimentDebugger.enable()`](#experimentdebuggerenable)
6566
- [`experimentDebugger.disable()`](#experimentdebuggerdisable)
6667
- [`mixpanelHelper`](#mixpanelhelper)
@@ -575,10 +576,21 @@ Removes the listener subscription and prevents future callbacks.
575576
576577
Debugging tool. Attaches a fixed-position panel to the bottom of the `<body>` element that displays mounted experiments and enables the user to change active variants in real-time.
577578
578-
The debugger is wrapped in a conditional `if(process.env.NODE_ENV === "production") {...}` and will not display on production builds using [envify](https://github.com/hughsk/envify).
579+
The debugger is wrapped in a conditional `if(process.env.NODE_ENV === "production") {...}` and will not display on production builds using [envify](https://github.com/hughsk/envify). This can be overriden by `setDebuggerAvailable`
579580
580581
<img src="https://cdn.rawgit.com/pushtell/react-ab-test/master/documentation-images/debugger-animated-2.gif" width="325" height="325" />
581582
583+
#### `experimentDebugger.setDebuggerAvailable(isAvailable)`
584+
Overrides `process.env.NODE_ENV` check, so it can be decided if the debugger is available
585+
or not at runtime. This allow, for instance, to enable the debugger in a testing environment but not in production.
586+
Note that you require to explicitly call to `.enable` even if you forced this to be truthy.
587+
588+
* **Return Type:** No return value
589+
* **Parameters:**
590+
* `isAvailable` - Tells whether the debugger is available or not
591+
* **Required**
592+
* **Type:** `boolean`
593+
582594
#### `experimentDebugger.enable()`
583595
584596
Attaches the debugging panel to the `<body>` element.

src/debugger.jsx

Lines changed: 155 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,176 @@
11
import React, { Component } from 'react';
22
import ReactDOM from 'react-dom';
33
import emitter from "./emitter";
4-
import {canUseDOM} from 'fbjs/lib/ExecutionEnvironment';
4+
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
55

6-
if(process.env.NODE_ENV === "production" || !canUseDOM) {
7-
module.exports = {
8-
enable() {},
9-
disable() {}
10-
}
11-
} else {
12-
let style = null;
13-
function attachStyleSheet() {
14-
style = document.createElement("style");
15-
style.appendChild(document.createTextNode(""));
16-
document.head.appendChild(style);
17-
function addCSSRule(selector, rules) {
18-
if("insertRule" in style.sheet) {
19-
style.sheet.insertRule(selector + "{" + rules + "}", 0);
20-
} else if("addRule" in style.sheet) {
21-
style.sheet.addRule(selector, rules, 0);
22-
}
6+
let isDebuggerAvailable = process.env.NODE_ENV !== "production";
7+
8+
let style = null;
9+
function attachStyleSheet() {
10+
style = document.createElement("style");
11+
style.appendChild(document.createTextNode(""));
12+
document.head.appendChild(style);
13+
function addCSSRule(selector, rules) {
14+
if ("insertRule" in style.sheet) {
15+
style.sheet.insertRule(selector + "{" + rules + "}", 0);
16+
} else if ("addRule" in style.sheet) {
17+
style.sheet.addRule(selector, rules, 0);
2318
}
24-
addCSSRule("#pushtell-debugger", "z-index: 25000");
25-
addCSSRule("#pushtell-debugger", "position: fixed");
26-
addCSSRule("#pushtell-debugger", "transform: translateX(-50%)");
27-
addCSSRule("#pushtell-debugger", "bottom: 0");
28-
addCSSRule("#pushtell-debugger", "left: 50%");
29-
addCSSRule("#pushtell-debugger ul", "margin: 0");
30-
addCSSRule("#pushtell-debugger ul", "padding: 0 0 0 20px");
31-
addCSSRule("#pushtell-debugger li", "margin: 0");
32-
addCSSRule("#pushtell-debugger li", "padding: 0");
33-
addCSSRule("#pushtell-debugger li", "font-size: 14px");
34-
addCSSRule("#pushtell-debugger li", "line-height: 14px");
35-
addCSSRule("#pushtell-debugger input", "float: left");
36-
addCSSRule("#pushtell-debugger input", "margin: 0 10px 0 0");
37-
addCSSRule("#pushtell-debugger input", "padding: 0");
38-
addCSSRule("#pushtell-debugger input", "cursor: pointer");
39-
addCSSRule("#pushtell-debugger label", "color: #999999");
40-
addCSSRule("#pushtell-debugger label", "margin: 0 0 10px 0");
41-
addCSSRule("#pushtell-debugger label", "cursor: pointer");
42-
addCSSRule("#pushtell-debugger label", "font-weight: normal");
43-
addCSSRule("#pushtell-debugger label.active", "color: #000000");
44-
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "font-size: 16px");
45-
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "color: #000000");
46-
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "margin: 0 0 10px 0");
47-
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "font-size: 10px");
48-
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "color: #999999");
49-
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "text-align: center");
50-
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "margin: 10px -40px 0 -10px");
51-
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "border-top: 1px solid #b3b3b3");
52-
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "padding: 10px 10px 5px 10px");
53-
addCSSRule("#pushtell-debugger .pushtell-handle", "cursor: pointer");
54-
addCSSRule("#pushtell-debugger .pushtell-handle", "padding: 5px 10px 5px 10px");
55-
addCSSRule("#pushtell-debugger .pushtell-panel", "padding: 15px 40px 5px 10px");
56-
addCSSRule("#pushtell-debugger .pushtell-container", "font-size: 11px");
57-
addCSSRule("#pushtell-debugger .pushtell-container", "background-color: #ebebeb");
58-
addCSSRule("#pushtell-debugger .pushtell-container", "color: #000000");
59-
addCSSRule("#pushtell-debugger .pushtell-container", "box-shadow: 0px 0 5px rgba(0, 0, 0, 0.1)");
60-
addCSSRule("#pushtell-debugger .pushtell-container", "border-top: 1px solid #b3b3b3");
61-
addCSSRule("#pushtell-debugger .pushtell-container", "border-left: 1px solid #b3b3b3");
62-
addCSSRule("#pushtell-debugger .pushtell-container", "border-right: 1px solid #b3b3b3");
63-
addCSSRule("#pushtell-debugger .pushtell-container", "border-top-left-radius: 2px");
64-
addCSSRule("#pushtell-debugger .pushtell-container", "border-top-right-radius: 2px");
65-
addCSSRule("#pushtell-debugger .pushtell-close", "cursor: pointer");
66-
addCSSRule("#pushtell-debugger .pushtell-close", "font-size: 16px");
67-
addCSSRule("#pushtell-debugger .pushtell-close", "font-weight: bold");
68-
addCSSRule("#pushtell-debugger .pushtell-close", "color: #CC0000");
69-
addCSSRule("#pushtell-debugger .pushtell-close", "position: absolute");
70-
addCSSRule("#pushtell-debugger .pushtell-close", "top: 0px");
71-
addCSSRule("#pushtell-debugger .pushtell-close", "right: 7px");
72-
addCSSRule("#pushtell-debugger .pushtell-close:hover", "color: #FF0000");
73-
addCSSRule("#pushtell-debugger .pushtell-close, #pushtell-debugger label", "transition: all .25s");
7419
}
75-
function removeStyleSheet() {
76-
if(style !== null){
77-
document.head.removeChild(style);
78-
style = null;
79-
}
20+
addCSSRule("#pushtell-debugger", "z-index: 25000");
21+
addCSSRule("#pushtell-debugger", "position: fixed");
22+
addCSSRule("#pushtell-debugger", "transform: translateX(-50%)");
23+
addCSSRule("#pushtell-debugger", "bottom: 0");
24+
addCSSRule("#pushtell-debugger", "left: 50%");
25+
addCSSRule("#pushtell-debugger ul", "margin: 0");
26+
addCSSRule("#pushtell-debugger ul", "padding: 0 0 0 20px");
27+
addCSSRule("#pushtell-debugger li", "margin: 0");
28+
addCSSRule("#pushtell-debugger li", "padding: 0");
29+
addCSSRule("#pushtell-debugger li", "font-size: 14px");
30+
addCSSRule("#pushtell-debugger li", "line-height: 14px");
31+
addCSSRule("#pushtell-debugger input", "float: left");
32+
addCSSRule("#pushtell-debugger input", "margin: 0 10px 0 0");
33+
addCSSRule("#pushtell-debugger input", "padding: 0");
34+
addCSSRule("#pushtell-debugger input", "cursor: pointer");
35+
addCSSRule("#pushtell-debugger label", "color: #999999");
36+
addCSSRule("#pushtell-debugger label", "margin: 0 0 10px 0");
37+
addCSSRule("#pushtell-debugger label", "cursor: pointer");
38+
addCSSRule("#pushtell-debugger label", "font-weight: normal");
39+
addCSSRule("#pushtell-debugger label.active", "color: #000000");
40+
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "font-size: 16px");
41+
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "color: #000000");
42+
addCSSRule("#pushtell-debugger .pushtell-experiment-name", "margin: 0 0 10px 0");
43+
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "font-size: 10px");
44+
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "color: #999999");
45+
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "text-align: center");
46+
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "margin: 10px -40px 0 -10px");
47+
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "border-top: 1px solid #b3b3b3");
48+
addCSSRule("#pushtell-debugger .pushtell-production-build-note", "padding: 10px 10px 5px 10px");
49+
addCSSRule("#pushtell-debugger .pushtell-handle", "cursor: pointer");
50+
addCSSRule("#pushtell-debugger .pushtell-handle", "padding: 5px 10px 5px 10px");
51+
addCSSRule("#pushtell-debugger .pushtell-panel", "padding: 15px 40px 5px 10px");
52+
addCSSRule("#pushtell-debugger .pushtell-container", "font-size: 11px");
53+
addCSSRule("#pushtell-debugger .pushtell-container", "background-color: #ebebeb");
54+
addCSSRule("#pushtell-debugger .pushtell-container", "color: #000000");
55+
addCSSRule("#pushtell-debugger .pushtell-container", "box-shadow: 0px 0 5px rgba(0, 0, 0, 0.1)");
56+
addCSSRule("#pushtell-debugger .pushtell-container", "border-top: 1px solid #b3b3b3");
57+
addCSSRule("#pushtell-debugger .pushtell-container", "border-left: 1px solid #b3b3b3");
58+
addCSSRule("#pushtell-debugger .pushtell-container", "border-right: 1px solid #b3b3b3");
59+
addCSSRule("#pushtell-debugger .pushtell-container", "border-top-left-radius: 2px");
60+
addCSSRule("#pushtell-debugger .pushtell-container", "border-top-right-radius: 2px");
61+
addCSSRule("#pushtell-debugger .pushtell-close", "cursor: pointer");
62+
addCSSRule("#pushtell-debugger .pushtell-close", "font-size: 16px");
63+
addCSSRule("#pushtell-debugger .pushtell-close", "font-weight: bold");
64+
addCSSRule("#pushtell-debugger .pushtell-close", "color: #CC0000");
65+
addCSSRule("#pushtell-debugger .pushtell-close", "position: absolute");
66+
addCSSRule("#pushtell-debugger .pushtell-close", "top: 0px");
67+
addCSSRule("#pushtell-debugger .pushtell-close", "right: 7px");
68+
addCSSRule("#pushtell-debugger .pushtell-close:hover", "color: #FF0000");
69+
addCSSRule("#pushtell-debugger .pushtell-close, #pushtell-debugger label", "transition: all .25s");
70+
}
71+
function removeStyleSheet() {
72+
if (style !== null) {
73+
document.head.removeChild(style);
74+
style = null;
8075
}
76+
}
8177

82-
class Debugger extends Component {
83-
state = {
84-
experiments: emitter.getActiveExperiments(),
85-
visible: false
86-
};
78+
class Debugger extends Component {
79+
state = {
80+
experiments: emitter.getActiveExperiments(),
81+
visible: false
82+
};
8783

88-
toggleVisibility = () => {
89-
this.setState({
90-
visible: !this.state.visible
91-
});
92-
}
84+
toggleVisibility = () => {
85+
this.setState({
86+
visible: !this.state.visible
87+
});
88+
}
9389

94-
updateExperiments = () => {
95-
this.setState({
96-
experiments: emitter.getActiveExperiments()
97-
});
98-
}
90+
updateExperiments = () => {
91+
this.setState({
92+
experiments: emitter.getActiveExperiments()
93+
});
94+
}
9995

100-
setActiveVariant(experimentName, variantName) {
101-
emitter.setActiveVariant(experimentName, variantName);
102-
}
96+
setActiveVariant(experimentName, variantName) {
97+
emitter.setActiveVariant(experimentName, variantName);
98+
}
99+
100+
componentWillMount() {
101+
this.activeSubscription = emitter.addListener("active", this.updateExperiments);
102+
this.inactiveSubscription = emitter.addListener("inactive", this.updateExperiments);
103+
}
104+
105+
componentWillUnmount() {
106+
this.activeSubscription.remove();
107+
this.inactiveSubscription.remove();
108+
}
103109

104-
componentWillMount(){
105-
this.activeSubscription = emitter.addListener("active", this.updateExperiments);
106-
this.inactiveSubscription = emitter.addListener("inactive", this.updateExperiments);
110+
render() {
111+
var experimentNames = Object.keys(this.state.experiments);
112+
if (this.state.visible) {
113+
return <div className="pushtell-container pushtell-panel">
114+
<div className="pushtell-close" onClick={this.toggleVisibility}>×</div>
115+
{experimentNames.map(experimentName => {
116+
var variantNames = Object.keys(this.state.experiments[experimentName]);
117+
if (variantNames.length === 0) {
118+
return;
119+
}
120+
return <div className="pushtell-experiment" key={experimentName}>
121+
<div className="pushtell-experiment-name">{experimentName}</div>
122+
<ul>
123+
{variantNames.map(variantName => {
124+
return <li key={variantName}>
125+
<label className={this.state.experiments[experimentName][variantName] ? "active" : null} onClick={this.setActiveVariant.bind(this, experimentName, variantName)}>
126+
<input type="radio" name={experimentName} value={variantName} defaultChecked={this.state.experiments[experimentName][variantName]} />
127+
{variantName}
128+
</label>
129+
</li>
130+
})}
131+
</ul>
132+
</div>;
133+
})}
134+
<div className="pushtell-production-build-note">This panel is hidden on production builds.</div>
135+
</div>;
136+
} else if (experimentNames.length > 0) {
137+
return <div className="pushtell-container pushtell-handle" onClick={this.toggleVisibility}>
138+
{experimentNames.length} Active Experiment{experimentNames.length > 1 ? "s" : ""}
139+
</div>;
140+
} else {
141+
return null;
107142
}
143+
}
144+
}
108145

109-
componentWillUnmount(){
110-
this.activeSubscription.remove();
111-
this.inactiveSubscription.remove();
146+
147+
module.exports = {
148+
setDebuggerAvailable(value) {
149+
isDebuggerAvailable = value;
150+
},
151+
enable() {
152+
if (!isDebuggerAvailable || !canUseDOM) {
153+
return;
112154
}
113155

114-
render(){
115-
var experimentNames = Object.keys(this.state.experiments);
116-
if(this.state.visible) {
117-
return <div className="pushtell-container pushtell-panel">
118-
<div className="pushtell-close" onClick={this.toggleVisibility}>×</div>
119-
{experimentNames.map(experimentName => {
120-
var variantNames = Object.keys(this.state.experiments[experimentName]);
121-
if(variantNames.length === 0) {
122-
return;
123-
}
124-
return <div className="pushtell-experiment" key={experimentName}>
125-
<div className="pushtell-experiment-name">{experimentName}</div>
126-
<ul>
127-
{variantNames.map(variantName => {
128-
return <li key={variantName}>
129-
<label className={this.state.experiments[experimentName][variantName] ? "active" : null} onClick={this.setActiveVariant.bind(this, experimentName, variantName)}>
130-
<input type="radio" name={experimentName} value={variantName} defaultChecked={this.state.experiments[experimentName][variantName]} />
131-
{variantName}
132-
</label>
133-
</li>
134-
})}
135-
</ul>
136-
</div>;
137-
})}
138-
<div className="pushtell-production-build-note">This panel is hidden on production builds.</div>
139-
</div>;
140-
} else if(experimentNames.length > 0){
141-
return <div className="pushtell-container pushtell-handle" onClick={this.toggleVisibility}>
142-
{experimentNames.length} Active Experiment{experimentNames.length > 1 ? "s" : ""}
143-
</div>;
144-
} else {
145-
return null;
146-
}
156+
attachStyleSheet();
157+
let body = document.getElementsByTagName('body')[0];
158+
let container = document.createElement('div');
159+
container.id = 'pushtell-debugger';
160+
body.appendChild(container);
161+
ReactDOM.render(<Debugger />, container);
162+
},
163+
disable() {
164+
if (!isDebuggerAvailable || !canUseDOM) {
165+
return;
147166
}
148-
}
149167

150-
module.exports = {
151-
enable() {
152-
attachStyleSheet();
153-
let body = document.getElementsByTagName('body')[0];
154-
let container = document.createElement('div');
155-
container.id = 'pushtell-debugger';
156-
body.appendChild(container);
157-
ReactDOM.render(<Debugger />, container);
158-
},
159-
disable() {
160-
removeStyleSheet();
161-
let body = document.getElementsByTagName('body')[0];
162-
let container = document.getElementById('pushtell-debugger');
163-
if(container) {
164-
ReactDOM.unmountComponentAtNode(container);
165-
body.removeChild(container);
166-
}
168+
removeStyleSheet();
169+
let body = document.getElementsByTagName('body')[0];
170+
let container = document.getElementById('pushtell-debugger');
171+
if (container) {
172+
ReactDOM.unmountComponentAtNode(container);
173+
body.removeChild(container);
167174
}
168175
}
169176
}

test/browser/debugger.test.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,19 @@ describe('Debugger', () => {
6868

6969
experimentDebugger.disable();
7070
});
71+
72+
describe('when is not available', () => {
73+
beforeEach(() => {
74+
experimentDebugger.setDebuggerAvailable(false);
75+
experimentDebugger.enable();
76+
});
77+
it('should do nothing when enabling it', () => {
78+
expect(hasCSSSelector('#pushtell-debugger')).toBe(false);
79+
});
80+
afterEach(() => {
81+
experimentDebugger.setDebuggerAvailable(true);
82+
});
83+
});
7184
});
7285

7386
// See http://stackoverflow.com/a/985070

0 commit comments

Comments
 (0)