Igal Steklov
CEO & FED
import React, { Component } from 'react';
import featureToggles from 'services/featureToggles';
class ExampleComp extends Component {
render () {
return featureToggles.isEnabled('my_new_feature')
? <a href="...">Click me for new feature</a>
: <p>New feature is coming soon</p>
}
}
services/featureToggles.js
import bulletTrain from 'bullet-train-client';
featureToggles.isEnabled('my_feature') // true | false
import bulletTrain from 'bullet-train-client';
const config = {
environmentID: process.env.BULLET_TRAIN_ENV_ID
};
class FeatureToggles {
// Called once app loaded and user data retrieved
init(user) {
bulletTrain.init(config);
bulletTrain.identify(`${user.id}_${user.fullName}`);
}
// Called on logout from our app
destroy() {
bulletTrain.logout();
}
isEnabled(featureToggle) {
return bulletTrain.hasFeature(featureToggle);
}
}
export default new FeatureToggles();
[
{
"id":3000,
"feature":{
"id":818,
"name":"notes_list",
"created_date":"2019-05-25T15:22:26.058231Z",
"initial_value":null,
"description":null,
"default_enabled":true,
"type":"FLAG",
"project":325
},
"feature_state_value":null,
"enabled":true,
"environment":814,
"identity":null
},
{ ... },
{ ... }
]
~ curl 'https://api.bullet-train.io/api/v1/flags/' -H 'X-Environment-Key: BgwB3E6'
<body data-ft-new_design>
...
</body>
.button {
font-size: 12px;
}
body[data-ft-new_design] .button {
font-size: 18px;
}
.button {
font-size: 12px;
@include when-feature-enabled("new_design") {
// Compiles to body[data-ft-new_design] .button
font-size: 18px;
}
}
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import featureToggles from 'services/featureToggles';
class Navigation extends Component {
render () {
return (
<nav>
<ul>
<li><NavLink to="/">Homepage</NavLink></li>
<li><NavLink to="/reports">Reports</NavLink></li>
{featureToggles.isEnabled('notes_list') &&
<li>
<NavLink to="/notes">
Notes
</NavLink>
</li>
}
</ul>
</nav>
);
}
}
import React, { Component } from 'react';
import history from 'utils/history';
import featureToggles from 'services/featureToggles';
class NotesList extends Component {
//UNSAFE_componentWillMount
constructor(props) {
super(props);
if (!featureToggles.isEnabled('notes_list')) {
history.push('/error-page/404');
return;
}
}
render () {
return null;
}
}
import React, { Component } from 'react';
import history from 'utils/history';
import featureToggles from 'services/featureToggles';
class NotesList extends Component {
//UNSAFE_componentWillMount
constructor(props) {
super(props);
if (!featureToggles.isEnabled('notes_list')) {
history.push('/error-page/404');
return;
}
}
render () {
return (
<div>
Notes list component is under development,
so no user reach current component, yay 🎉.
</div>
);
}
}
AB Test or just Release
import React, { Component, Fragment } from 'react';
import { Switch, Route } from 'react-router-dom';
import featureToggles from 'services/featureToggles';
import DashboardPage from 'containers/DashboardPage';
import ListPage from 'containers/ListPage';
import ListPageV2 from 'containers/ListPageV2';
export default class App extends Component {
render() {
return (
<main>
<Switch>
<Route path="/" component={DashboardPage}/>
<Route path="/list" render={() => featureToggles.isEnabled('list_page_v2')
? <ListPageV2/>
: <ListPage/>
}/>
</Switch>
</main>
);
}
}
import axios from 'axios';
import featureToggles from 'services/featureToggles';
export function listNotes() {
const apiVersion = featureToggles.isEnabled('notes_api_v2')
? 'v2'
: 'v1';
return axios.get(`api/${apiVersion}/notes/`);
}
class NotesList extends Component {
render () {
const { notes = [] } = this.props;
if (featureToggles.isEnabled('notes_list')) {
return (
<ul>
{notes.map(note =>
<li key={note.id}>{note.text}</li>
)}
</ul>
);
}
return (<p>Notes are coming soon</p>);
}
}
class NotesList extends Component {
render () {
const { notes = [] } = this.props;
const isNotesExist = notes.length > 0;
if (isNotesExist || featureToggles.isEnabled('notes_list')) {
return (
<ul>
{notes.map(note =>
<li key={note.id}>{note.text}</li>
)}
</ul>
);
}
return (<p>Notes are coming soon</p>);
}
}
class NotesList extends Component {
render () {
const { notes = [] } = this.props;
const isNotesExist = notes.length > 0;
if (isNotesExist || featureToggles.isEnabled('notes_list')) {
return (
<ul>
{notes.map(note =>
<li key={note.id}>{note.text}</li>
)}
</ul>
);
}
return (<p>Notes are coming soon</p>);
}
}
class NotesList extends Component {
render () {
const { notes = [] } = this.props;
return (
<ul>
{notes.map(note =>
<li key={note.id}>{note.text}</li>
)}
</ul>
);
}
}
Igal Steklov
igal@webiya.co.il
+972-54-6490123
Slides at: tiny.cc/FTDD