June 14, InfluxDays event

How we extended InfluxData’s React application before InfluxDays 2018

Sébastien Léger
Loud ML
Published in
6 min readJun 27, 2018

--

A Reactjs and Go challenge

The Loud ML (loudml.io) team had received constant requests to integrate machine learning tools into InfluxData’s React application, so we dived right in. We were impressed with the clarity and quality of React’s code, which made implementation of our extension fast and simple. The result? Loud ML functionality right inside Chronograph! Here’s how we did it.

Loud ML software natively uses InfluxData’s InfluxDB time series database. InfluxData uses Chronograf — an open-source web application written in the popular Go language and React.js languages that provides tools to visualize monitored data and easily create alerts and automation. Loud ML was only available as a toolbox in API mode. There was no user interface to create and manipulate models (such as training or prediction).

It was sensible to integrate Loud ML with Chronograf, but with InfluxDays fast approaching, we knew we had our work cut out.

The good news is we did it — on-time! And now everyone can enjoy it.

If you’d like to learn more about how we did it, such as the changes in the React and Go source code and the considerations along the way, buckle your belts and read on.

Learning about the components

Chronograf uses React/Redux to manage its UI. React lets us build user interfaces with declarative syntax and build encapsulated components. Redux lets us manage the global state of an application and share it between components.

Chronograf offers a React/JSX style guide inspired by the Airbnb style guide. The coding styles are fully respected and, even without reading the guide, it’s possible to acquire good practices and to train in how to build a complex React app.

Coding the new component

First things first, let’s add a navigation link

Loud ML is a new component embedded in the application. We added a new icon and menu item in the navigation bar, and this can be done through a single file:

SideNav.js

<NavBlockicon=”loudml-bold”link={`${sourcePrefix}/loudml`}location={location}><NavHeader link={`${sourcePrefix}/loudml`} title={‘LoudML’} /></NavBlock>

This reverse proxy thing: how the components are routed together

Routing the user to a given page relies on two principles:

  • UI routing
  • API routing

UI routing is a simple yet efficient way to define page transitions. The example below defines two pages: a main page where all ML model information is present, and another page to let the user create or edit new models.

index.js

import {LoudMLPage, ModelPage} from ‘src/loudml’<Route path=”/sources/:sourceID” component={UserIsAuthenticated(App)}><Route component={CheckSources}><Route path=”status” component={StatusPage} /><Route path=”loudml” component={LoudMLPage} /><Route path=”loudml/models/new” component={ModelPage} /><Route path=”loudml/models/:name/edit” component={ModelPage}/>

Loud ML, like several other back-end components, provides a standard CRUD interface over the HTTP protocol that lets users control, add, delete, train machine learning models and generate forecasts. But on which host and IP address is it running?

A React application should not include static IP addresses since this information can change from one host to another. Therefore, make sure you use:

  • a relative URL in the React components when calling Loud ML
  • a reverse proxy (more about it below!)

Staying on track with reverse proxy routing

When running Chronograf in a local development environment, you can edit the devServer.proxy section of the dev.config.js file. So, internally InfluxData’s application will use webpack to route the given URL to the right destinations:

proxy: {
‘/chronograf/v1’: {
target: ‘http://localhost:8888',
secure: false,
},
‘/loudml/api’: {
target: ‘http://localhost:8077',
secure: false,
pathRewrite: {
‘^/loudml/api’: ‘/’,
},
},

Here, all /loudml/api calls will be routed to http://localhost:8077. The pathRewrite command allows you to change the URL and remove the prefix “/loudml/api’.

Things are different in production mode. InfluxData’s Web application is written in Go at its core. You will find the reverse proxy most useful in files mux.go and proxy.go.

Adding routes in mux.go gives us all we need to ensure all network components can communicate seamlessly over the IP network:

// LoudML Proxyrouter.GET(“/loudml/api”, EnsureViewer(service.LoudMLProxyGet))router.GET(“/loudml/api/*path”, EnsureViewer(service.LoudMLProxyGet))router.POST(“/loudml/api/*path”, EnsureEditor(service.LoudMLProxyPost))router.PATCH(“/loudml/api/*path”, EnsureEditor(service.LoudMLProxyPatch))router.PUT(“/loudml/api/*path”, EnsureEditor(service.LoudMLProxyPut))router.DELETE(“/loudml/api/*path”, EnsureEditor(service.LoudMLProxyDelete))

Then, in proxy.go, declare a service and HTTP methods (the GET method is shown in the example below):

// LoudMLProxy proxy requests to loudmldfunc (s *Service) LoudMLProxy(w http.ResponseWriter, r *http.Request) {    path := r.URL.Path    // To preserve any HTTP query arguments to the LoudML path,
// we concat and parse them into u.
loudMLURL := os.Getenv(“LOUDML_URL”)
path = strings.TrimPrefix(path, “/loudml/api”)
uri := singleJoiningSlash(loudMLURL, path)
u, err := url.Parse(uri)
if err != nil {
msg := fmt.Sprintf(“Error parsing LoudML URL: %v”, err)
Error(w, http.StatusUnprocessableEntity, msg, s.Logger)
return
}
director := func(req *http.Request) {
// Set the Host header of the original Kapacitor URL
req.Host = u.Host
req.URL = u
req.URL.RawQuery = r.URL.RawQuery
}
proxy := &httputil.ReverseProxy{
Director: director,
FlushInterval: time.Second,
}
proxy.ServeHTTP(w, r)
}
// LoudMLProxyGet proxies GET to loudmldfunc (s *Service) LoudMLProxyGet(w http.ResponseWriter, r *http.Request) {
s.LoudMLProxy(w, r)
}

Last but not least: how to keep consistency in UX design

The React application source code includes out-of-the-box components providing a compelling user experience. For example:

  • MenuTooltipButton component usage with confirm action
  • DeleteConfirmTableCell component usage with cancel/confirm action

The easiest approach is to use them directly and if required, implement the missing features. In the example below, we wrote a dedicated time picker component with shortcut selection for future dates: next week, next month, etc. This enables the user to forecast future time-series values — a critical machine learning feature, for example, when you are planning predictive maintenance.

Requesting a forecast eg, for next week using the edited time picker component

Below, a forecasting example adding (red and blue) forecast values for hits on a server, and the average response times expected.

Red and blue lines are produced by the forecasting job

Adding dynamic notifications

We completed our extension with notifications to let the user know about machine learning task completion. And in the upcoming release of Loud ML, they can also be used to inform the user about the time-series anomalies detected automatically by the machine learning jobs!

Displaying notifications when jobs are completed

Chronograf provides a series of Redux actions to notify the user of successful and failing operations. These actions are available in shared/actions/ (notification.js and errors.js). Notifications appear on the right-hand side of the page as colored flags. Below is an example of an API call with notification turned on. Notifications are set with different display times depending on the type.

Action type

import {FIVE_SECONDS, TEN_SECONDS, INFINITE} from ‘shared/constants/index’const notifySuccess = message => ({
type: ‘success’,
icon: ‘checkmark’,
duration: FIVE_SECONDS,
message,
})
const notifyError = (duration, message) => ({
type: ‘error’,
icon: ‘alert-triangle’,
duration,
message,
})
export const notifyModelDeleted = name => notifySuccess(`model ${name} deleted successfully`)export const notifyModelDeleteFailed = (name, message) => notifyError(
TEN_SECONDS,
`cannot delete ‘${name}’ model: ${message}`,

Component type

import {notify as notifyAction} from ‘shared/actions/notifications’import {
notifyModelDeleted,
notifyModelDeleteFailed,
} from ‘src/loudml/actions/notifications’
import * as api from ‘src/loudml/apis’import {
modelDeleted as modelDeletedAction,
} from “src/loudml/actions/view”
class LoudMLPage extends Component {
constructor(props) {
super(props)
}
deleteModel = name => () => {
const {
modelActions: {modelDeleted},
notify
} = this.props
api.deleteModel(name)
.then(() => {
modelDeleted(name)
notify(notifyModelDeleted(name))
})
.catch(error => {
notify(notifyModelDeleteFailed(name, error))
})
}
const mapDispatchToProps = dispatch => ({
modelActions: {
modelDeleted: name => dispatch(modelDeletedAction(name)),
},
notify: message => dispatch(notifyAction(message)),
})
export default connect(null, mapDispatchToProps)(LoudMLPage)

Measuring our success

By using standard components and conventions, this new application looks and feels like the original InfluxData application, so we achieved our main goal.

We’re proud to have contributed to this React development and to have the opportunity to integrate smart ML features into InfluxData’s application. Please feel free to contribute and join the community: the code is open source! And if you know of another React application that would benefit from Loud ML machine learning and its capabilities, please let us know: loudml.io

If you’d like to see the code in action, visit https://github.com/regel/chronograf and download the Docker image for Loud ML https://hub.docker.com/r/loudml/.

Sébastien, Vianney, Samuel, Christophe, and the Loud ML team.

--

--

Sébastien Léger
Loud ML

Sébastien is passionate about artificial intelligence, which inspired him to build Loud ML — software that enables engineers to implement AI-driven applications