moved devServer into a module so that it can be used programmatically

This commit is contained in:
Spencer Alger 2014-06-12 15:35:06 -07:00
parent c4c8af0675
commit 7ace826b63
26 changed files with 500 additions and 109 deletions

0
.creds.json Normal file
View file

View file

@ -6,22 +6,22 @@
"main": "Gulpfile.js",
"dependencies": {},
"devDependencies": {
"bluebird": "~2.0.7",
"connect": "~2.19.5",
"event-stream": "~3.1.5",
"expect.js": "~0.2.0",
"grunt": "~0.4.2",
"grunt-cli": "~0.1.13",
"grunt-contrib-connect": "~0.6.0",
"grunt-contrib-jade": "~0.10.0",
"grunt-contrib-jshint": "~0.8.0",
"grunt-contrib-less": "~0.10.0",
"grunt-contrib-requirejs": "~0.4.4",
"grunt-contrib-watch": "~0.5.3",
"grunt-mocha": "~0.4.10",
"http-proxy": "~1.1.4",
"istanbul": "~0.2.4",
"load-grunt-config": "~0.7.0",
"lodash": "~2.4.1",
"bluebird": "~1.2.4",
"mocha": "~1.17.1",
"event-stream": "~3.1.5"
"mocha": "~1.17.1"
},
"scripts": {
"test": "grunt test",

View file

@ -1,4 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
.thumbnail > img,
.thumbnail a > img,
.carousel-inner > .item > img,

View file

@ -323,8 +323,6 @@ define(function (require) {
};
}, {});
if (!indexPattern) return;
var columnObjects = arrayToKeys($state.columns);
$scope.fields = [];
@ -332,6 +330,8 @@ define(function (require) {
$scope.formatsByName = {};
$state.columns = $state.columns || [];
if (!indexPattern) return;
// Inject source into list;
//$scope.fields.push({name: '_source', type: 'source', display: false});

View file

@ -26,6 +26,7 @@ define(function (require) {
template: headerHtml,
controller: function ($scope) {
$scope.headerClass = function (column) {
if (!$scope.mapping) return;
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
var sorting = $scope.sorting;

View file

@ -1,4 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
.thumbnail > img,
.thumbnail a > img,
.carousel-inner > .item > img,

View file

@ -1,4 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
.thumbnail > img,
.thumbnail a > img,
.carousel-inner > .item > img,

View file

@ -66,10 +66,12 @@ define(function (require) {
var row = rowStack.slice(0);
var metric = bucket.value == null ? bucket.doc_count : bucket.value;
if (revColStack.length) [].push.apply(row, new Array(revColStack.length));
// we have a full row, minus the final metric
row.push(metric);
if (!revColStack.length) {
// we have a full row, minus the final metric
row.push(metric);
} else {
[].push.apply(row, new Array(revColStack.length + 1));
}
chartData.rows.push(row);
};
@ -184,8 +186,6 @@ define(function (require) {
rows: []
};
// debugger;
(function cleanup(obj) {
if (obj.rows && obj.columns) {
// this obj is a chart
@ -196,10 +196,11 @@ define(function (require) {
var rows = obj.rows;
var cols = obj.columns;
delete obj.rows;
delete obj.columns;
obj.label = [].concat(labelStack, obj.label).filter(Boolean).join(' > ');
obj.label = [].concat(labelStack, obj.label).filter(Boolean).join(' > ');
rows.forEach(function (row) {
raw.rows.push([].concat(raw.splitValStack, row));
});
@ -210,6 +211,7 @@ define(function (require) {
delete obj.splits;
labelStack.push(obj.label);
_.forOwn(splits, function (split) {
raw.splitColumns.push(split.column);
raw.splitValStack.push(split.value);

View file

@ -100,15 +100,6 @@ define(function (require) {
custom: true
};
agg.params.min_doc_count = {
hide: true,
custom: true,
default: 0,
write: function (selection, output) {
output.aggParams.min_doc_count = 0;
}
};
agg.params.extended_bounds = {
hide: true,
default: {},

View file

@ -1,4 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
.thumbnail > img,
.thumbnail a > img,
.carousel-inner > .item > img,

View file

@ -1,4 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
.thumbnail > img,
.thumbnail a > img,
.carousel-inner > .item > img,

View file

@ -56,8 +56,6 @@ define(function (require) {
return;
}
if (newPage === oldPage) return;
// setup the list of the other pages to link to
$scope.otherPages = [];
var width = +getOtherWidth($scope) || 5;

View file

@ -1,4 +1,3 @@
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
@import "icon_font.css";
/*!
* Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome

View file

@ -2,7 +2,7 @@
// Bootswatch
// -----------------------------------------------------
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
// @import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
// Navbar =====================================================================

View file

@ -1,75 +0,0 @@
module.exports = function (grunt) {
var instrumentationMiddleware = require('../utils/instrumentation');
var amdRapperMiddleware = require('../utils/amd_rapper');
return {
dev: {
options: {
hostname: '0.0.0.0',
middleware: function (connect, options, stack) {
stack = stack || [];
var root = grunt.config.get('root');
// when a request for an intrumented file comes in (?instrument=true)
// and it is included in `pattern`, it will be handled
// by this middleware
stack.push(instrumentationMiddleware({
// root that files should be served from
root: root,
// make file names easier to read
displayRoot: grunt.config.get('src'),
// filter the filenames that will be served
filter: function (filename) {
// return true if the filename should be
// included in the coverage report (results are cached)
return grunt.file.isMatch([
'**/src/**/*.js',
'!**/src/bower_components/**/*',
'!**/src/kibana/utils/{event_emitter,next_tick}.js'
], filename);
}
}));
// minimize code duplication (especially in the istanbul reporter)
// by allowing node_modules to be requested in an AMD rapper
stack.push(amdRapperMiddleware({
root: root
}));
// standard static middleware reading from the root
stack.push(connect.static(root));
stack.push(function (req, res, next) {
if (req.method !== 'HEAD' || req.url !== '/') return next();
res.statusCode === 200;
res.setHeader('Pong', 'Kibana 4 Dev Server');
res.end();
});
// redirect requests for '/' to '/src/'
stack.push(function (req, res, next) {
if (req.url !== '/') return next();
res.statusCode = 303;
res.setHeader('Location', '/src/');
res.end();
});
// allow browsing directories
stack.push(
function (req, res, next) {
// prevent chrome's stupid "this page is in spanish"
res.setHeader('Content-Language', 'en');
next();
},
connect.directory(root)
);
return stack;
}
}
}
};
};

View file

@ -19,7 +19,7 @@ module.exports = function (grunt) {
}
function onError() {
grunt.task.run(['connect:dev']);
grunt.task.run(['server']);
done();
}

View file

@ -1,3 +1,10 @@
module.exports = function (grunt) {
grunt.registerTask('server', ['connect:dev:keepalive']);
grunt.registerTask('server', function () {
var done = this.async();
var DevServer = require('../test/utils/dev_server');
var server = new DevServer();
server.listen(8000, function () {
console.log('visit http://localhost:8000');
});
});
};

31
test.js Normal file
View file

@ -0,0 +1,31 @@
var Proxy = require('./test/utils/proxy');
var Promise = require('bluebird');
var configfile = require('fs').readFileSync(__dirname + '/src/config.js', 'utf8').replace(
/elasticsearch:[^\n]+/,
'elasticsearch: \'http://\' + window.location.host + \'/es-proxy\','
);
var p = new Proxy();
p.on('/es-proxy', {
target: 'http://localhost:9200',
middleware: function (req, res) {
// strip the prefix
req.url = req.url.replace(/^\/es-proxy/, '');
}
});
p.on('/src/config.js', {
target: 'http://localhost:8000',
middleware: function (req, res) {
// overwrite the contents of the file
res.end(configfile);
}
});
// send all other requests to localhost:8000
p.on('*', 'http://localhost:8000');
p.listen()
.then(console.log.bind(console, 'listening on %s'))
.catch(console.error.bind(console));

66
test/e2e/index.js Normal file
View file

@ -0,0 +1,66 @@
/* jshint node:true */
var _ = require('lodash');
var Proxy = require('./test/utils/proxy');
var Promise = require('bluebird');
var readFileSync = require('fs').readFileSync;
var rel = require('path').join.bind(null, __dirname);
var soda = require('soda');
var expect = require('chai').expect;
// array of async fns that will be called when we need to close up
var closers = [];
connectSauceLabs({
username: SAUCE_USERNAME,
accessKey: SAUCE_ACCESSKEY,
verbose: true,
//optionally change sauce connect logfile location
logfile: null,
// optionally identity the tunnel for concurrent tunnels
tunnelIdentifier: null,
logger: function () {
console.log(' \x1b[33mSauce Connect\x1b[0m: %s', [].join.call(arguments, ', '));
}
})
.then(function (connectProc) {
closers.push(function () {
return Promise.promisify(connectProc.close, connectProc)()
.then(console.log.bind(console, 'Closed Sauce Connect'));
});
console.log('Started Sauce Connect');
})
.then(function () {
var browser = soda.createSauceClient({
'url': 'http://localhost:8000',
'username': SAUCE_USERNAME,
'access-key': SAUCE_ACCESSKEY,
'os': 'Windows 2003',
'browser': 'firefox',
'browser-version': '7',
'name': 'This is an example test'
});
browser.on('command', function(cmd, args){
console.log(' \x1b[33m%s\x1b[0m: %s', cmd, args.join(', '));
});
browser
.chain
.session()
.open('/src/#/visualize')
.waitForPageToLoad(8000)
.select('//ng-model="indexPattern.selection"')
.end(function(err){
this.queue = null;
this.setContext('sauce:job-info={"passed": ' + (err === null) + '}', function(){
browser.testComplete(function(){
if (err) throw err;
});
});
Promise.all(closers.map(function (fn) { return fn(); }));
});
});

View file

@ -0,0 +1,41 @@
var Promise = require('bluebird');
var connectSauceLabs = Promise.promisify(require('sauce-connect-launcher'));
var creds = require('../../../.creds');
module.exports = function SauceLabsConnector() {
var proc;
var pickPort = function () {
return new Promise(function (resolve, reject) {
var server = require('http').createServer();
server.listen(0, function () {
var port = server.address().port;
server.once('close', function () {
resolve(port);
});
server.close();
});
});
};
this.listen = function () {
connectSauceLabs({
username: creds.SAUCE_USERNAME,
accessKey: creds.SAUCE_ACCESSKEY,
verbose: true,
tunnelIdentifier: null,
logger: function () {
console.log(' \x1b[33mSauce Connect\x1b[0m: %s', [].join.call(arguments, ', '));
}
})
.then(function (connectProc) {
proc = connectProc;
return proc;
});
};
this.close = function () {
};
};

View file

@ -0,0 +1,50 @@
var wd = require('wd');
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
var asserters = wd.asserters;
var mocha = require('mocha');
chai.use(chaiAsPromised);
chai.should();
chaiAsPromised.transferPromiseness = wd.transferPromiseness;
var url = 'http://localhost:8000';
var access_key = '1770de66-20c2-4f3c-9d3e-3ecf01d768f4';
var username = 'ccowan';
var remotePort = (process.env.SAUCE) ? 4445 : 4444;
var options = {
browserName: 'firefox',
name: 'Example Marvel Test'
};
before(function (done) {
this.browser = wd.promiseChainRemote('localhost', remotePort, username, access_key)
.init(options)
.then(function () { done(); });
});
after(function (done) {
this.browser.quit()
.then(function () { done(); });
});
describe('Welcome Screen', function () {
describe('click "Continue Free Trial"', function() {
beforeEach(function (done) {
this.browser
.get(url+'/kibana/index.html#/dashboard/elasticsearch/marvel.overview.json')
.then(function () { done(); });
});
it('should set the marvelOpts trialTimestamp attribute in localStorage', function(done) {
this.browser
.waitForElementByCss('div > .modal-body > p > a:nth-child(3)', asserters.isDisplayed, 1000)
.click()
.waitForConditionInBrowser('JSON.parse(localStorage.getItem("marvelOpts")).status === "trial"', 1000)
.then(function () { done(); });
});
});
});

View file

@ -0,0 +1,108 @@
/* jshint node:true */
var _ = require('lodash');
var Promise = require('bluebird');
var portOptions = (
'4000,4001,4040,4321,4502,4503,4567,5000,5001,5050,5555,5432,6000,6001,6060,' +
'6666,6543,7000,7070,7774,7777,8000,8001,8003,8031,8080,8081,8443,8765,8777,' +
'8888,9000,9001,9031,9080,9090,9876,9877,9999,49221,55001'
).split(',').map(function (p) { return _.parseInt(p); });
module.exports = function SauceLabsProxy(opts) {
if (!(this instanceof SauceLabsProxy)) return new SauceLabsProxy(opts);
var proxy = this;
var pass = require('http-proxy').createProxyServer({});
var routes = [];
var send404 = function (req, res) {
res.statusCode = 404;
res.end('unable to find ' + req.url);
};
var sendErr = function (req, res, err) {
res.statusCode = 500;
res.end('error: ' + err.message);
};
var server = require('http').createServer(function(req, res) {
var route = _.find(routes, function (route) {
return route.re.test(req.url);
});
if (!route) return send404(req, res);
var origEnd = res.end;
var respEnded = false;
res.end = function () {
respEnded = true;
origEnd.apply(res, arguments);
};
Promise.cast(route.middleware && route.middleware(req, res))
.then(function (ret) {
if (!respEnded) pass.web(req, res, { target: route.target });
})
.catch(function (err) {
return sendErr(res, res, err);
});
});
proxy.on = function (prefix, opts) {
var route = {};
if (_.isRegExp(prefix)) {
route.re = prefix;
}
else if (prefix === '*') {
route.re = /.*/;
}
else {
route.re = new RegExp('^' + prefix);
}
if (_.isString(opts)) {
route.target = opts;
}
if (_.isPlainObject(opts)) {
_.assign(route, opts);
}
routes.push(route);
return route;
};
proxy.listen = function () {
return new Promise(function (resolve, reject) {
var port;
var nextPort = function () {
port = portOptions.shift();
if (!port) done(new Error('No more port options available.'));
server.listen(port);
};
var onError = function (err) {
if (err && err.code === 'EADDRINUSE') nextPort();
else done(err);
};
var onListening = function () {
done();
};
var done = function (err) {
server.removeListener('error', onError);
server.removeListener('listening', onListening);
if (err) reject(err);
else resolve(port);
};
server.on('error', onError, true);
server.on('listening', onListening, true);
nextPort();
});
};
};

View file

@ -0,0 +1,71 @@
/* jshint node:true */
var wd = require('wd');
var Promise = require('bluebird');
var readFileSync = require('fs').readFileSync;
var rel = require('path').join.bind(null, __dirname);
var Connector = require('./_connector');
var DevServer = require('./_dev_server');
var portOptions = [
4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555, 5432, 6000, 6001, 6060,
6666, 6543, 7000, 7070, 7774, 7777, 8000, 8001, 8003, 8031, 8080, 8081, 8443, 8765, 8777,
8888, 9000, 9001, 9031, 9080, 9090, 9876, 9877, 9999, 49221, 55001
];
var configfile = readFileSync(rel('../../src/config.js'), 'utf8').replace(
/elasticsearch:[^\n]+/,
'elasticsearch: \'http://\' + window.location.host + \'/es-proxy\','
);
exports.init = function (opts) {
opts = opts || {};
var server = new DevServer();
if (opts.useSauceLabs) {
var connector = new Connector();
}
return server.listen()
.then(function (port) {
return wd.promiseChainRemote()
.init({
browserName: browserName || 'chrome',
baseUrl: 'http://localhost:' + port
});
})
.then(function (wd) {
return {
server: server,
wd: wd,
close: function () {
return Promise.all([
wd.close
server.close()
]);
}
}
});
};
exports.usingSauceLabs = function () {
var server = new DevServer();
var close = function () {
return Promise.all([
proxy.close(),
connector.close(),
server.close()
]);
};
return server.listen()
.then(function (devServerPort) {
return connector.listen()
})
.then(function () {
});
});
};

View file

@ -0,0 +1,106 @@
/* jshint node:true */
var connect = require('connect');
var http = require('http');
var Promise = require('bluebird');
var instrumentationMiddleware = require('./_instrumentation');
var amdRapperMiddleware = require('./_amd_rapper');
var rel = require('path').join.bind(null, __dirname);
var proxy = require('http-proxy').createProxyServer({});
var ROOT = rel('../../../');
var SRC = rel('../../../src');
module.exports = function DevServer(opts) {
opts = opts || {};
var server = this;
var app = connect();
var httpServer = http.createServer(app);
app.use(instrumentationMiddleware({
root: ROOT,
displayRoot: SRC,
filter: function (filename) {
return filename.match(/.*\/src\/.*\.js$/)
&& !filename.match(/.*\/src\/bower_components\/.*\.js$/)
&& !filename.match(/.*\/src\/kibana\/utils\/(event_emitter|next_tick)\.js$/);
}
}));
app.use(amdRapperMiddleware({
root: ROOT
}));
app.use('/es-proxy', function (req, res) {
req.url = req.url.replace(/^\/es-proxy/, '');
proxy.web(req, res, { target: 'http://localhost:' + (process.env.ES_PORT || 9200) });
});
app.use(connect.static(ROOT));
// respond to the "maybe_start_server" pings
app.use(function (req, res, next) {
if (req.method !== 'HEAD' || req.url !== '/') return next();
res.statusCode === 200;
res.setHeader('Pong', 'Kibana 4 Dev Server');
res.end();
});
app.use(function (req, res, next) {
if (req.url !== '/') return next();
res.statusCode = 303;
res.setHeader('Location', '/src/');
res.end();
});
// prevent chrome's stupid "this page is in spanish" on the directories page
app.use(function (req, res, next) {
res.setHeader('Content-Language', 'en');
next();
});
// allow browsing directories
app.use(connect.directory(ROOT));
server.listenOnFirstOpenPort = function (ports) {
var options = ports.slice(0);
// wrap this logic in an IIFE so that we can call it again later
return (function attempt() {
var port = options.shift();
if (!port) return Promise.reject(new Error('None of the supplied options succeeded'));
return server.listen(port)
// filter out EADDRINUSE errors and call attempt again
.catch(function (err) {
if (err.code === 'EADDRINUSE') return attempt();
throw err;
});
})();
};
server.listen = function (port) {
return new Promise(function (resolve, reject) {
var done = function (err) {
httpServer.removeListener('error', done);
httpServer.removeListener('listening', done);
// pass the error along
if (err) return reject(err);
resolve(server.port = httpServer.address().port);
};
// call done with an error
httpServer.on('error', done, true);
// call done without any args
httpServer.on('listening', done, true);
httpServer.listen(port);
});
};
server.close = httpServer.close.bind(httpServer);
};