Skip to content

Instantly share code, notes, and snippets.

@demmer
Last active January 28, 2016 17:28
Show Gist options
  • Save demmer/df3d67fb36ccb5f6382c to your computer and use it in GitHub Desktop.
Save demmer/df3d67fb36ccb5f6382c to your computer and use it in GitHub Desktop.
Juttle API Proxy Example

Juttle API Proxy

This is a simple proof-of-concept example of how one could embed Juttle-powered charts and visualizations in a custom application.

Installing / Running

Requires node.js to be installed.

# git clone https://gist.github.com/df3d67fb36ccb5f6382c.git juttle-api-proxy
# cd juttle-api-proxy
# npm install
# npm install outrigger
# ./node_modules/.bin/webpack
# node ./node_modules/.bin/outriggerd --root / &
# node ./server.js &

Then browse to http://localhost:8080 and follow the instructions in the app.

How it works

The application implements a simple session-based login in which the "company" is specified by the app and stored in the session store.

When the "run" button is clicked, the endpoint /api/revenue/ looks up the company in the current login session and includes executes a known juttle program on the outrigger system running on localhost:8080.

Once the juttle completes, the output is massaged slightly and the data points are returned from the API request. The client-side application then takes the data and populates it into a timechart and a table.

var JuttleViz = require('juttle-viz');
var fetch = require('isomorphic-fetch');
var moment = require('moment');
var JSDP = require('juttle-jsdp');
function setStatus(message) {
var loginStatus = document.querySelector('#loginStatus');
loginStatus.textContent = message;
}
function getLogin() {
fetch('/api/company', {
credentials: 'same-origin',
})
.then(function(response) {
if (response.status === 200) {
response.json()
.then(function(result) {
setStatus('logged in as ' + result.company);
});
} else if (response.status == 401) {
setStatus('not logged in');
} else {
console.error('error getting login status');
}
});
}
getLogin();
function submitLogin(e) {
var company = document.querySelector('#company').value;
fetch('/api/login', {
method: 'post',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({company: company})
})
.then(function() {
getLogin();
});
return false;
}
document.querySelector('#loginForm').onsubmit = submitLogin;
var timechart, table;
function initCharts() {
var timechartContainer = document.querySelector('#timechart');
if (timechart) {
timechart.destroy();
timechartContainer.removeChild(timechartContainer.childNodes[0]);
}
timechart = new JuttleViz.Timechart({
juttleEnv: {
now: new Date()
},
params: {
title: 'timechart title',
display: {
dataDensity: 0
}
}
});
timechartContainer.appendChild(timechart.visuals[0]);
timechart.setDimensions(null, 500);
var tableContainer = document.querySelector('#table');
if (table) {
table.destroy();
tableContainer.removeChild(tableContainer.childNodes[0]);
}
table = new JuttleViz.Table({
juttleEnv: {
now: new Date()
},
params: {
title: 'a table'
}
});
tableContainer.appendChild(table.visuals[0]);
table.setDimensions(null, tableContainer.style.width, tableContainer.style.height);
}
function runJuttle() {
var growth = document.querySelector('#growth').value;
fetch('/api/revenue?growth=' + growth, {
credentials: 'same-origin',
})
.then(function(response) {
if (response.status !== 200) {
return response.text()
.then(function(text) {
throw new Error(text || response.statusText);
});
}
return response.json()
})
.then(function(data) {
initCharts();
data = JSDP.deserialize(data);
timechart.consume(data);
table.consume(data);
})
.catch(function(err) {
setStatus(err.toString());
});
}
document.querySelector('#run').onclick = function(e) {
e.preventDefault();
runJuttle();
}
<html>
<head>
<title>Juttle API Proxy Example</title>
<link rel="stylesheet" type="text/css" href='/node_modules/juttle-viz/build/charts.css'/>
</head>
<body>
<h3>Login</h3>
<p>First "log in" by entering a company name in the login box and submitting the
form. This will set a session cookie remembering the company that the "user"
belongs to.</p>
<form id='loginForm'>
<input type='text' id='company' placeholder='company'></input>
<input type='submit' id='login' value='Login'></input>
<span id='loginStatus'></span>
</form>
<h3>Juttle</h3>
<p>
Once logged in, click the "Run Juttle" button to execute the juttle.
The Growth Factor input value is passed as a query parameter to the API, which
in turn passes it to the juttle program as an input control. The company name is
extracted from the login session and is also included as an input value to set
the company name in the timeseries of points.
</p>
<label>Revenue Growth Factor</label>
<input type='text' id='growth' value="2"></input>
<input type='button' id='run' value='Run' onclick='runJuttle'></input>
<div style="clear: all;"></div>
<div style="float: left">
<h3>Juttle-Powered Timechart</h3>
<div id='timechart' style='width: 500; height: 400;'></div>
</div>
<div style="float: left; padding-left: 20px;">
<h3>Juttle-Powered Table</h3>
<div id='table' style='width: 750;'></div>
</div>
<script src='/build/app.js'></script>
</body>
</html>
{
"name": "juttle-api-proxy",
"version": "0.0.0",
"description": "This is a simple proof-of-concept example of how one could embed Juttle-powered charts and visualizations in a custom application.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://gist.github.com/df3d67fb36ccb5f6382c.git"
},
"author": "",
"license": "MIT",
"bugs": {
"url": "https://gist.github.com/df3d67fb36ccb5f6382c"
},
"homepage": "https://gist.github.com/df3d67fb36ccb5f6382c",
"dependencies": {
"bluebird": "^3.1.5",
"body-parser": "^1.14.2",
"browserify": "^13.0.0",
"express": "^4.13.4",
"express-session": "^1.13.0",
"isomorphic-fetch": "^2.2.1",
"json-loader": "^0.5.4",
"juttle-jsdp": "^0.1.1",
"juttle-viz": "^0.3.1",
"moment": "^2.11.1",
"webpack": "^1.12.12",
"websocket": "^1.0.22"
}
}
input company: text -label "Company";
input growth: number -label "Growth" -default 2;
emit -from :1 year ago: -every :1 day:
| put value=Math.pow(count(), growth)
| put #company=company
| view timechart
var express = require('express');
var session = require('express-session');
var bodyParser = require('body-parser');
var fetch = require('isomorphic-fetch');
var app = express();
app.use(session({
secret: 'sekret',
resave: true,
saveUninitialized: true
}));
// Get the currently logged-in company
app.get('/api/company', function(req, res) {
if (! req.session.company) {
res.status(401).send();
} else {
res.status(200).json({company: req.session.company});
}
});
// Log in as the specified company
app.post('/api/login', bodyParser.json(), function(req, res) {
req.session.company = req.body.company;
res.status(200).send();
});
// Run the saved revenue.juttle program, accepting a query parameter for the
// time range as a query parameter, and adding in an implicit input for the
// logged-in company.
app.get('/api/revenue', function(req, res) {
var company = req.session.company;
if (!company) {
return res.status(401).send('Must be logged in');
}
var inputs = {
growth: parseInt(req.query.growth),
company: company
};
fetch('http://localhost:8080/api/v0/jobs', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
path: __dirname + '/revenue.juttle',
wait: true,
inputs: inputs
})
})
.then(function(result) {
if (result.status !== 200) {
return result.text()
.then(function(text) {
console.log(text)
throw new Error('error running juttle: ' + text);
})
}
return result.json();
})
.then(function(data) {
var points = data.output.sink0.data;
points = points.map(function(pt) {
return pt.point;
});
res.json(points);
})
.catch(function(err) {
res.status(500).send(err.toString());
})
});
app.use(express.static(__dirname));
var port = process.env.PORT || 8000;
app.listen(port, function() {
console.log('listening on http://localhost:' + port);
});
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: {
'app': './app.js'
},
output: {
path: path.join(__dirname, 'build'),
filename: '[name].js'
},
plugins: [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.NoErrorsPlugin()
],
module: {
loaders: [
{
test: /\.json$/,
include: __dirname,
loader: 'json'
}
]
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment