mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
Merge branch 'master' into feature/console
This commit is contained in:
commit
3ec3006d38
118 changed files with 1252 additions and 871 deletions
13
package.json
13
package.json
|
@ -24,18 +24,19 @@
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"author": "Rashid Khan <rashid.khan@elastic.co>",
|
"author": "Rashid Khan <rashid.khan@elastic.co>",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
"Spencer Alger <spencer.alger@elastic.co>",
|
|
||||||
"Matt Bargar <matt.bargar@elastic.co>",
|
|
||||||
"Jon Budzenski <jonathan.budzenski@elastic.co>",
|
|
||||||
"Chris Cowan <chris.cowan@elastic.co>",
|
"Chris Cowan <chris.cowan@elastic.co>",
|
||||||
"Court Ewing <court@elastic.co>",
|
"Court Ewing <court@elastic.co>",
|
||||||
|
"Jim Unger <jim.unger@elastic.co>",
|
||||||
"Joe Fleming <joe.fleming@elastic.co>",
|
"Joe Fleming <joe.fleming@elastic.co>",
|
||||||
|
"Jon Budzenski <jonathan.budzenski@elastic.co>",
|
||||||
|
"Juan Thomassie <juan.thomassie@elastic.co>",
|
||||||
"Khalah Jones-Golden <khalah.jones@elastic.co>",
|
"Khalah Jones-Golden <khalah.jones@elastic.co>",
|
||||||
"Lukas Olson <lukas.olson@elastic.co>",
|
"Lukas Olson <lukas.olson@elastic.co>",
|
||||||
"Juan Thomassie <juan.thomassie@elastic.co>",
|
"Matt Bargar <matt.bargar@elastic.co>",
|
||||||
|
"Nicolás Bevacqua <nico@elastic.co>",
|
||||||
"Shelby Sturgis <shelby@elastic.co>",
|
"Shelby Sturgis <shelby@elastic.co>",
|
||||||
"Tim Sullivan <tim@elastic.co>",
|
"Spencer Alger <spencer.alger@elastic.co>",
|
||||||
"Jim Unger <jim.unger@elastic.co>"
|
"Tim Sullivan <tim@elastic.co>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt test",
|
"test": "grunt test",
|
||||||
|
|
44
src/cli/cluster/__tests__/_mock_cluster_fork.js
Normal file
44
src/cli/cluster/__tests__/_mock_cluster_fork.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
import { assign, random } from 'lodash';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import cluster from 'cluster';
|
||||||
|
import { delay } from 'bluebird';
|
||||||
|
|
||||||
|
export default class MockClusterFork extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
let dead = true;
|
||||||
|
|
||||||
|
function wait() {
|
||||||
|
return delay(random(10, 250));
|
||||||
|
}
|
||||||
|
|
||||||
|
assign(this, {
|
||||||
|
process: {
|
||||||
|
kill: sinon.spy(() => {
|
||||||
|
(async () => {
|
||||||
|
await wait();
|
||||||
|
this.emit('disconnect');
|
||||||
|
await wait();
|
||||||
|
dead = true;
|
||||||
|
this.emit('exit');
|
||||||
|
cluster.emit('exit', this, this.exitCode || 0);
|
||||||
|
}());
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
isDead: sinon.spy(() => dead),
|
||||||
|
send: sinon.stub()
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.spy(this, 'on');
|
||||||
|
sinon.spy(this, 'removeListener');
|
||||||
|
sinon.spy(this, 'emit');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await wait();
|
||||||
|
dead = false;
|
||||||
|
this.emit('online');
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
}
|
59
src/cli/cluster/__tests__/cluster_manager.js
Normal file
59
src/cli/cluster/__tests__/cluster_manager.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import expect from 'expect.js';
|
||||||
|
import sinon from 'auto-release-sinon';
|
||||||
|
import cluster from 'cluster';
|
||||||
|
import { ChildProcess } from 'child_process';
|
||||||
|
import { sample, difference } from 'lodash';
|
||||||
|
|
||||||
|
import ClusterManager from '../cluster_manager';
|
||||||
|
import Worker from '../worker';
|
||||||
|
|
||||||
|
describe('CLI cluster manager', function () {
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
sinon.stub(cluster, 'fork', function () {
|
||||||
|
return {
|
||||||
|
process: {
|
||||||
|
kill: sinon.stub(),
|
||||||
|
},
|
||||||
|
isDead: sinon.stub().returns(false),
|
||||||
|
removeListener: sinon.stub(),
|
||||||
|
on: sinon.stub(),
|
||||||
|
send: sinon.stub()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const manager = new ClusterManager({});
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('has two workers', function () {
|
||||||
|
const manager = setup();
|
||||||
|
|
||||||
|
expect(manager.workers).to.have.length(2);
|
||||||
|
for (const worker of manager.workers) expect(worker).to.be.a(Worker);
|
||||||
|
|
||||||
|
expect(manager.optimizer).to.be.a(Worker);
|
||||||
|
expect(manager.server).to.be.a(Worker);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('delivers broadcast messages to other workers', function () {
|
||||||
|
const manager = setup();
|
||||||
|
|
||||||
|
for (const worker of manager.workers) {
|
||||||
|
Worker.prototype.start.call(worker);// bypass the debounced start method
|
||||||
|
worker.onOnline();
|
||||||
|
}
|
||||||
|
|
||||||
|
const football = {};
|
||||||
|
const messenger = sample(manager.workers);
|
||||||
|
|
||||||
|
messenger.emit('broadcast', football);
|
||||||
|
for (const worker of manager.workers) {
|
||||||
|
if (worker === messenger) {
|
||||||
|
expect(worker.fork.send.callCount).to.be(0);
|
||||||
|
} else {
|
||||||
|
expect(worker.fork.send.firstCall.args[0]).to.be(football);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
198
src/cli/cluster/__tests__/worker.js
Normal file
198
src/cli/cluster/__tests__/worker.js
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
import expect from 'expect.js';
|
||||||
|
import sinon from 'auto-release-sinon';
|
||||||
|
import cluster from 'cluster';
|
||||||
|
import { ChildProcess } from 'child_process';
|
||||||
|
import { difference, findIndex, sample } from 'lodash';
|
||||||
|
import { fromNode as fn } from 'bluebird';
|
||||||
|
|
||||||
|
import MockClusterFork from './_mock_cluster_fork';
|
||||||
|
import Worker from '../worker';
|
||||||
|
|
||||||
|
const workersToShutdown = [];
|
||||||
|
|
||||||
|
function assertListenerAdded(emitter, event) {
|
||||||
|
sinon.assert.calledWith(emitter.on, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertListenerRemoved(emitter, event) {
|
||||||
|
sinon.assert.calledWith(
|
||||||
|
emitter.removeListener,
|
||||||
|
event,
|
||||||
|
emitter.on.args[findIndex(emitter.on.args, { 0: event })][1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup(opts = {}) {
|
||||||
|
sinon.stub(cluster, 'fork', function () {
|
||||||
|
return new MockClusterFork();
|
||||||
|
});
|
||||||
|
|
||||||
|
const worker = new Worker(opts);
|
||||||
|
workersToShutdown.push(worker);
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CLI cluster manager', function () {
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
for (const worker of workersToShutdown) {
|
||||||
|
if (worker.shutdown.restore) {
|
||||||
|
// if the shutdown method was stubbed, restore it first
|
||||||
|
worker.shutdown.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
await worker.shutdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#onChange', function () {
|
||||||
|
context('opts.watch = true', function () {
|
||||||
|
it('restarts the fork', function () {
|
||||||
|
const worker = setup({ watch: true });
|
||||||
|
sinon.stub(worker, 'start');
|
||||||
|
worker.onChange('/some/path');
|
||||||
|
expect(worker.changes).to.eql(['/some/path']);
|
||||||
|
sinon.assert.calledOnce(worker.start);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('opts.watch = false', function () {
|
||||||
|
it('does not restart the fork', function () {
|
||||||
|
const worker = setup({ watch: false });
|
||||||
|
sinon.stub(worker, 'start');
|
||||||
|
worker.onChange('/some/path');
|
||||||
|
expect(worker.changes).to.eql([]);
|
||||||
|
sinon.assert.notCalled(worker.start);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#shutdown', function () {
|
||||||
|
context('after starting()', function () {
|
||||||
|
it('kills the worker and unbinds from message, online, and disconnect events', async function () {
|
||||||
|
const worker = setup();
|
||||||
|
await worker.start();
|
||||||
|
expect(worker).to.have.property('online', true);
|
||||||
|
const fork = worker.fork;
|
||||||
|
sinon.assert.notCalled(fork.process.kill);
|
||||||
|
assertListenerAdded(fork, 'message');
|
||||||
|
assertListenerAdded(fork, 'online');
|
||||||
|
assertListenerAdded(fork, 'disconnect');
|
||||||
|
worker.shutdown();
|
||||||
|
sinon.assert.calledOnce(fork.process.kill);
|
||||||
|
assertListenerRemoved(fork, 'message');
|
||||||
|
assertListenerRemoved(fork, 'online');
|
||||||
|
assertListenerRemoved(fork, 'disconnect');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('before being started', function () {
|
||||||
|
it('does nothing', function () {
|
||||||
|
const worker = setup();
|
||||||
|
worker.shutdown();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#parseIncomingMessage()', function () {
|
||||||
|
context('on a started worker', function () {
|
||||||
|
it(`is bound to fork's message event`, async function () {
|
||||||
|
const worker = setup();
|
||||||
|
await worker.start();
|
||||||
|
sinon.assert.calledWith(worker.fork.on, 'message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores non-array messsages', function () {
|
||||||
|
const worker = setup();
|
||||||
|
worker.parseIncomingMessage('some string thing');
|
||||||
|
worker.parseIncomingMessage(0);
|
||||||
|
worker.parseIncomingMessage(null);
|
||||||
|
worker.parseIncomingMessage(undefined);
|
||||||
|
worker.parseIncomingMessage({ like: 'an object' });
|
||||||
|
worker.parseIncomingMessage(/weird/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls #onMessage with message parts', function () {
|
||||||
|
const worker = setup();
|
||||||
|
const stub = sinon.stub(worker, 'onMessage');
|
||||||
|
worker.parseIncomingMessage([10, 100, 1000, 10000]);
|
||||||
|
sinon.assert.calledWith(stub, 10, 100, 1000, 10000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#onMessage', function () {
|
||||||
|
context('when sent WORKER_BROADCAST message', function () {
|
||||||
|
it('emits the data to be broadcasted', function () {
|
||||||
|
const worker = setup();
|
||||||
|
const data = {};
|
||||||
|
const stub = sinon.stub(worker, 'emit');
|
||||||
|
worker.onMessage('WORKER_BROADCAST', data);
|
||||||
|
sinon.assert.calledWithExactly(stub, 'broadcast', data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when sent WORKER_LISTENING message', function () {
|
||||||
|
it('sets the listening flag and emits the listening event', function () {
|
||||||
|
const worker = setup();
|
||||||
|
const data = {};
|
||||||
|
const stub = sinon.stub(worker, 'emit');
|
||||||
|
expect(worker).to.have.property('listening', false);
|
||||||
|
worker.onMessage('WORKER_LISTENING');
|
||||||
|
expect(worker).to.have.property('listening', true);
|
||||||
|
sinon.assert.calledWithExactly(stub, 'listening');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when passed an unkown message', function () {
|
||||||
|
it('does nothing', function () {
|
||||||
|
const worker = setup();
|
||||||
|
worker.onMessage('asdlfkajsdfahsdfiohuasdofihsdoif');
|
||||||
|
worker.onMessage({});
|
||||||
|
worker.onMessage(23049283094);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#start', function () {
|
||||||
|
context('when not started', function () {
|
||||||
|
it('creates a fork and waits for it to come online', async function () {
|
||||||
|
const worker = setup();
|
||||||
|
|
||||||
|
sinon.spy(worker, 'on');
|
||||||
|
|
||||||
|
await worker.start();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(cluster.fork);
|
||||||
|
sinon.assert.calledWith(worker.on, 'fork:online');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('listens for cluster and process "exit" events', async function () {
|
||||||
|
const worker = setup();
|
||||||
|
|
||||||
|
sinon.spy(process, 'on');
|
||||||
|
sinon.spy(cluster, 'on');
|
||||||
|
|
||||||
|
await worker.start();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(cluster.on);
|
||||||
|
sinon.assert.calledWith(cluster.on, 'exit');
|
||||||
|
sinon.assert.calledOnce(process.on);
|
||||||
|
sinon.assert.calledWith(process.on, 'exit');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('when already started', function () {
|
||||||
|
it('calls shutdown and waits for the graceful shutdown to cause a restart', async function () {
|
||||||
|
const worker = setup();
|
||||||
|
await worker.start();
|
||||||
|
sinon.spy(worker, 'shutdown');
|
||||||
|
sinon.spy(worker, 'on');
|
||||||
|
|
||||||
|
worker.start();
|
||||||
|
sinon.assert.calledOnce(worker.shutdown);
|
||||||
|
sinon.assert.calledWith(worker.on, 'online');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,7 +11,7 @@ import BasePathProxy from './base_path_proxy';
|
||||||
process.env.kbnWorkerType = 'managr';
|
process.env.kbnWorkerType = 'managr';
|
||||||
|
|
||||||
module.exports = class ClusterManager {
|
module.exports = class ClusterManager {
|
||||||
constructor(opts, settings) {
|
constructor(opts = {}, settings = {}) {
|
||||||
this.log = new Log(opts.quiet, opts.silent);
|
this.log = new Log(opts.quiet, opts.silent);
|
||||||
this.addedCount = 0;
|
this.addedCount = 0;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import cluster from 'cluster';
|
import cluster from 'cluster';
|
||||||
let { resolve } = require('path');
|
import { resolve } from 'path';
|
||||||
let { EventEmitter } = require('events');
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import fromRoot from '../../utils/from_root';
|
import { BinderFor, fromRoot } from '../../utils';
|
||||||
|
|
||||||
let cliPath = fromRoot('src/cli');
|
let cliPath = fromRoot('src/cli');
|
||||||
let baseArgs = _.difference(process.argv.slice(2), ['--no-watch']);
|
let baseArgs = _.difference(process.argv.slice(2), ['--no-watch']);
|
||||||
|
@ -18,13 +18,6 @@ let dead = fork => {
|
||||||
return fork.isDead() || fork.killed;
|
return fork.isDead() || fork.killed;
|
||||||
};
|
};
|
||||||
|
|
||||||
let kill = fork => {
|
|
||||||
// fork.kill() waits for process to disconnect, but causes occasional
|
|
||||||
// "ipc disconnected" errors and is too slow for the proc's "exit" event
|
|
||||||
fork.process.kill();
|
|
||||||
fork.killed = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = class Worker extends EventEmitter {
|
module.exports = class Worker extends EventEmitter {
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
@ -36,26 +29,33 @@ module.exports = class Worker extends EventEmitter {
|
||||||
this.watch = (opts.watch !== false);
|
this.watch = (opts.watch !== false);
|
||||||
this.startCount = 0;
|
this.startCount = 0;
|
||||||
this.online = false;
|
this.online = false;
|
||||||
|
this.listening = false;
|
||||||
this.changes = [];
|
this.changes = [];
|
||||||
|
|
||||||
|
this.forkBinder = null; // defined when the fork is
|
||||||
|
this.clusterBinder = new BinderFor(cluster);
|
||||||
|
this.processBinder = new BinderFor(process);
|
||||||
|
|
||||||
let argv = _.union(baseArgv, opts.argv || []);
|
let argv = _.union(baseArgv, opts.argv || []);
|
||||||
this.env = {
|
this.env = {
|
||||||
kbnWorkerType: this.type,
|
kbnWorkerType: this.type,
|
||||||
kbnWorkerArgv: JSON.stringify(argv)
|
kbnWorkerArgv: JSON.stringify(argv)
|
||||||
};
|
};
|
||||||
|
|
||||||
_.bindAll(this, ['onExit', 'onMessage', 'onOnline', 'onDisconnect', 'shutdown', 'start']);
|
|
||||||
|
|
||||||
this.start = _.debounce(this.start, 25);
|
|
||||||
cluster.on('exit', this.onExit);
|
|
||||||
process.on('exit', this.shutdown);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onExit(fork, code) {
|
onExit(fork, code) {
|
||||||
if (this.fork !== fork) return;
|
if (this.fork !== fork) return;
|
||||||
|
|
||||||
|
// we have our fork's exit, so stop listening for others
|
||||||
|
this.clusterBinder.destroy();
|
||||||
|
|
||||||
// our fork is gone, clear our ref so we don't try to talk to it anymore
|
// our fork is gone, clear our ref so we don't try to talk to it anymore
|
||||||
this.fork = null;
|
this.fork = null;
|
||||||
|
this.forkBinder = null;
|
||||||
|
|
||||||
|
this.online = false;
|
||||||
|
this.listening = false;
|
||||||
|
this.emit('fork:exit');
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
this.log.bad(`${this.title} crashed`, 'with status code', code);
|
this.log.bad(`${this.title} crashed`, 'with status code', code);
|
||||||
|
@ -72,26 +72,48 @@ module.exports = class Worker extends EventEmitter {
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown() {
|
async shutdown() {
|
||||||
if (this.fork && !dead(this.fork)) {
|
if (this.fork && !dead(this.fork)) {
|
||||||
kill(this.fork);
|
// kill the fork
|
||||||
this.fork.removeListener('message', this.onMessage);
|
this.fork.process.kill();
|
||||||
this.fork.removeListener('online', this.onOnline);
|
this.fork.killed = true;
|
||||||
this.fork.removeListener('disconnect', this.onDisconnect);
|
|
||||||
|
// stop listening to the fork, it's just going to die
|
||||||
|
this.forkBinder.destroy();
|
||||||
|
|
||||||
|
// we don't need to react to process.exit anymore
|
||||||
|
this.processBinder.destroy();
|
||||||
|
|
||||||
|
// wait until the cluster reports this fork has exitted, then resolve
|
||||||
|
await new Promise(cb => this.once('fork:exit', cb));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(msg) {
|
parseIncomingMessage(msg) {
|
||||||
if (!_.isArray(msg) || msg[0] !== 'WORKER_BROADCAST') return;
|
if (!_.isArray(msg)) return;
|
||||||
this.emit('broadcast', msg[1]);
|
this.onMessage(...msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(type, data) {
|
||||||
|
switch (type) {
|
||||||
|
case 'WORKER_BROADCAST':
|
||||||
|
this.emit('broadcast', data);
|
||||||
|
break;
|
||||||
|
case 'WORKER_LISTENING':
|
||||||
|
this.listening = true;
|
||||||
|
this.emit('listening');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOnline() {
|
onOnline() {
|
||||||
this.online = true;
|
this.online = true;
|
||||||
|
this.emit('fork:online');
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisconnect() {
|
onDisconnect() {
|
||||||
this.online = false;
|
this.online = false;
|
||||||
|
this.listening = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
flushChangeBuffer() {
|
flushChangeBuffer() {
|
||||||
|
@ -102,9 +124,13 @@ module.exports = class Worker extends EventEmitter {
|
||||||
}, '');
|
}, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
async start() {
|
||||||
|
if (this.fork) {
|
||||||
// once "exit" event is received with 0 status, start() is called again
|
// once "exit" event is received with 0 status, start() is called again
|
||||||
if (this.fork) return this.shutdown();
|
this.shutdown();
|
||||||
|
await new Promise(cb => this.once('online', cb));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.changes.length) {
|
if (this.changes.length) {
|
||||||
this.log.warn(`restarting ${this.title}`, `due to changes in ${this.flushChangeBuffer()}`);
|
this.log.warn(`restarting ${this.title}`, `due to changes in ${this.flushChangeBuffer()}`);
|
||||||
|
@ -114,8 +140,20 @@ module.exports = class Worker extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fork = cluster.fork(this.env);
|
this.fork = cluster.fork(this.env);
|
||||||
this.fork.on('message', this.onMessage);
|
this.forkBinder = new BinderFor(this.fork);
|
||||||
this.fork.on('online', this.onOnline);
|
|
||||||
this.fork.on('disconnect', this.onDisconnect);
|
// when the fork sends a message, comes online, or looses it's connection, then react
|
||||||
|
this.forkBinder.on('message', (msg) => this.parseIncomingMessage(msg));
|
||||||
|
this.forkBinder.on('online', () => this.onOnline());
|
||||||
|
this.forkBinder.on('disconnect', () => this.onDisconnect());
|
||||||
|
|
||||||
|
// when the cluster says a fork has exitted, check if it is ours
|
||||||
|
this.clusterBinder.on('exit', (fork, code) => this.onExit(fork, code));
|
||||||
|
|
||||||
|
// when the process exits, make sure we kill our workers
|
||||||
|
this.processBinder.on('exit', () => this.shutdown());
|
||||||
|
|
||||||
|
// wait for the fork to report it is online before resolving
|
||||||
|
await new Promise(cb => this.once('fork:online', cb));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -82,7 +82,7 @@ Command.prototype.parseOptions = _.wrap(Command.prototype.parseOptions, function
|
||||||
|
|
||||||
Command.prototype.action = _.wrap(Command.prototype.action, function (action, fn) {
|
Command.prototype.action = _.wrap(Command.prototype.action, function (action, fn) {
|
||||||
return action.call(this, function (...args) {
|
return action.call(this, function (...args) {
|
||||||
var ret = fn.apply(this, args);
|
let ret = fn.apply(this, args);
|
||||||
if (ret && typeof ret.then === 'function') {
|
if (ret && typeof ret.then === 'function') {
|
||||||
ret.then(null, function (e) {
|
ret.then(null, function (e) {
|
||||||
console.log('FATAL CLI ERROR', e.stack);
|
console.log('FATAL CLI ERROR', e.stack);
|
||||||
|
|
|
@ -69,6 +69,6 @@ ${indent(cmd.optionHelp(), 2)}
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanReadableArgName(arg) {
|
function humanReadableArgName(arg) {
|
||||||
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
let nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||||
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
|
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
|
|
||||||
let legacySettingMap = {
|
let legacySettingMap = {
|
||||||
// server
|
// server
|
||||||
|
|
|
@ -3,7 +3,7 @@ const { isWorker } = require('cluster');
|
||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
|
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
|
|
||||||
let canCluster;
|
let canCluster;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -23,7 +23,7 @@ program
|
||||||
.command('help <command>')
|
.command('help <command>')
|
||||||
.description('get the help for a specific command')
|
.description('get the help for a specific command')
|
||||||
.action(function (cmdName) {
|
.action(function (cmdName) {
|
||||||
var cmd = _.find(program.commands, { _name: cmdName });
|
let cmd = _.find(program.commands, { _name: cmdName });
|
||||||
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
||||||
cmd.help();
|
cmd.help();
|
||||||
});
|
});
|
||||||
|
@ -35,7 +35,7 @@ program
|
||||||
});
|
});
|
||||||
|
|
||||||
// check for no command name
|
// check for no command name
|
||||||
var subCommand = argv[2] && !String(argv[2][0]).match(/^-|^\.|\//);
|
let subCommand = argv[2] && !String(argv[2][0]).match(/^-|^\.|\//);
|
||||||
if (!subCommand) {
|
if (!subCommand) {
|
||||||
program.defaultHelp();
|
program.defaultHelp();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import fromRoot from '../../../utils/from_root';
|
import { fromRoot } from '../../../utils';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { parseMilliseconds, parse, getPlatform } from '../settings';
|
import { parseMilliseconds, parse, getPlatform } from '../settings';
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
import install from './install';
|
import install from './install';
|
||||||
import Logger from '../lib/logger';
|
import Logger from '../lib/logger';
|
||||||
import pkg from '../../utils/package_json';
|
import pkg from '../../utils/package_json';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
import KbnServer from '../../server/kbn_server';
|
import KbnServer from '../../server/kbn_server';
|
||||||
import readYamlConfig from '../../cli/serve/read_yaml_config';
|
import readYamlConfig from '../../cli/serve/read_yaml_config';
|
||||||
import { versionSatisfies, cleanVersion } from './version';
|
import { versionSatisfies, cleanVersion } from './version';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
import list from './list';
|
import list from './list';
|
||||||
import Logger from '../lib/logger';
|
import Logger from '../lib/logger';
|
||||||
import { parse } from './settings';
|
import { parse } from './settings';
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
import remove from './remove';
|
import remove from './remove';
|
||||||
import Logger from '../lib/logger';
|
import Logger from '../lib/logger';
|
||||||
import { parse } from './settings';
|
import { parse } from './settings';
|
||||||
|
|
|
@ -17,23 +17,23 @@ export default function GeoHashGridAggResponseFixture() {
|
||||||
// },
|
// },
|
||||||
// });
|
// });
|
||||||
|
|
||||||
var geoHashCharts = _.union(
|
let geoHashCharts = _.union(
|
||||||
_.range(48, 57), // 0-9
|
_.range(48, 57), // 0-9
|
||||||
_.range(65, 90), // A-Z
|
_.range(65, 90), // A-Z
|
||||||
_.range(97, 122) // a-z
|
_.range(97, 122) // a-z
|
||||||
);
|
);
|
||||||
|
|
||||||
var totalDocCount = 0;
|
let totalDocCount = 0;
|
||||||
|
|
||||||
var tags = _.times(_.random(4, 20), function (i) {
|
let tags = _.times(_.random(4, 20), function (i) {
|
||||||
// random number of tags
|
// random number of tags
|
||||||
var docCount = 0;
|
let docCount = 0;
|
||||||
var buckets = _.times(_.random(40, 200), function () {
|
let buckets = _.times(_.random(40, 200), function () {
|
||||||
return _.sample(geoHashCharts, 3).join('');
|
return _.sample(geoHashCharts, 3).join('');
|
||||||
})
|
})
|
||||||
.sort()
|
.sort()
|
||||||
.map(function (geoHash) {
|
.map(function (geoHash) {
|
||||||
var count = _.random(1, 5000);
|
let count = _.random(1, 5000);
|
||||||
|
|
||||||
totalDocCount += count;
|
totalDocCount += count;
|
||||||
docCount += count;
|
docCount += count;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var results = {};
|
let results = {};
|
||||||
|
|
||||||
results.timeSeries = {
|
results.timeSeries = {
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var data = { };
|
let data = { };
|
||||||
|
|
||||||
data.metricOnly = {
|
data.metricOnly = {
|
||||||
hits: { total: 1000, hits: [], max_score: 0 },
|
hits: { total: 1000, hits: [], max_score: 0 },
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
var longString = Array(200).join('_');
|
let longString = Array(200).join('_');
|
||||||
|
|
||||||
export default function (id, mapping) {
|
export default function (id, mapping) {
|
||||||
function fakeVals(type) {
|
function fakeVals(type) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function stubbedLogstashFields() {
|
function stubbedLogstashFields() {
|
||||||
var sourceData = [
|
let sourceData = [
|
||||||
{ name: 'bytes', type: 'number', indexed: true, analyzed: true, sortable: true, filterable: true, count: 10 },
|
{ name: 'bytes', type: 'number', indexed: true, analyzed: true, sortable: true, filterable: true, count: 10 },
|
||||||
{ name: 'ssl', type: 'boolean', indexed: true, analyzed: true, sortable: true, filterable: true, count: 20 },
|
{ name: 'ssl', type: 'boolean', indexed: true, analyzed: true, sortable: true, filterable: true, count: 20 },
|
||||||
{ name: '@timestamp', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true, count: 30 },
|
{ name: '@timestamp', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true, count: 30 },
|
||||||
|
|
|
@ -3,11 +3,11 @@ import sinon from 'auto-release-sinon';
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
|
|
||||||
export default function (Private, Promise) {
|
export default function (Private, Promise) {
|
||||||
var indexPatterns = Private(FixturesStubbedLogstashIndexPatternProvider);
|
let indexPatterns = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||||
var getIndexPatternStub = sinon.stub();
|
let getIndexPatternStub = sinon.stub();
|
||||||
getIndexPatternStub.returns(Promise.resolve(indexPatterns));
|
getIndexPatternStub.returns(Promise.resolve(indexPatterns));
|
||||||
|
|
||||||
var courier = {
|
let courier = {
|
||||||
indexPatterns: { get: getIndexPatternStub },
|
indexPatterns: { get: getIndexPatternStub },
|
||||||
getStub: getIndexPatternStub
|
getStub: getIndexPatternStub
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
var keys = {};
|
let keys = {};
|
||||||
export default {
|
export default {
|
||||||
get: function (path, def) {
|
get: function (path, def) {
|
||||||
return keys[path] == null ? def : keys[path];
|
return keys[path] == null ? def : keys[path];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||||
|
|
||||||
function stubbedDocSourceResponse(Private) {
|
function stubbedDocSourceResponse(Private) {
|
||||||
var mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
let mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||||
|
|
||||||
return function (id, index) {
|
return function (id, index) {
|
||||||
index = index || '.kibana';
|
index = index || '.kibana';
|
||||||
|
|
|
@ -3,21 +3,21 @@ import TestUtilsStubIndexPatternProvider from 'test_utils/stub_index_pattern';
|
||||||
import IndexPatternsFieldTypesProvider from 'ui/index_patterns/_field_types';
|
import IndexPatternsFieldTypesProvider from 'ui/index_patterns/_field_types';
|
||||||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||||
export default function stubbedLogstashIndexPatternService(Private) {
|
export default function stubbedLogstashIndexPatternService(Private) {
|
||||||
var StubIndexPattern = Private(TestUtilsStubIndexPatternProvider);
|
let StubIndexPattern = Private(TestUtilsStubIndexPatternProvider);
|
||||||
var fieldTypes = Private(IndexPatternsFieldTypesProvider);
|
let fieldTypes = Private(IndexPatternsFieldTypesProvider);
|
||||||
var mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
let mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||||
|
|
||||||
|
|
||||||
var fields = mockLogstashFields.map(function (field) {
|
let fields = mockLogstashFields.map(function (field) {
|
||||||
field.displayName = field.name;
|
field.displayName = field.name;
|
||||||
var type = fieldTypes.byName[field.type];
|
let type = fieldTypes.byName[field.type];
|
||||||
if (!type) throw new TypeError('unknown type ' + field.type);
|
if (!type) throw new TypeError('unknown type ' + field.type);
|
||||||
if (!_.has(field, 'sortable')) field.sortable = type.sortable;
|
if (!_.has(field, 'sortable')) field.sortable = type.sortable;
|
||||||
if (!_.has(field, 'filterable')) field.filterable = type.filterable;
|
if (!_.has(field, 'filterable')) field.filterable = type.filterable;
|
||||||
return field;
|
return field;
|
||||||
});
|
});
|
||||||
|
|
||||||
var indexPattern = new StubIndexPattern('logstash-*', 'time', fields);
|
let indexPattern = new StubIndexPattern('logstash-*', 'time', fields);
|
||||||
indexPattern.id = 'logstash-*';
|
indexPattern.id = 'logstash-*';
|
||||||
|
|
||||||
return indexPattern;
|
return indexPattern;
|
||||||
|
|
|
@ -3,8 +3,8 @@ import searchResponse from 'fixtures/search_response';
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
|
|
||||||
export default function stubSearchSource(Private, $q, Promise) {
|
export default function stubSearchSource(Private, $q, Promise) {
|
||||||
var deferedResult = $q.defer();
|
let deferedResult = $q.defer();
|
||||||
var indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
let indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sort: sinon.spy(),
|
sort: sinon.spy(),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import VislibVisProvider from 'ui/vislib/vis';
|
import VislibVisProvider from 'ui/vislib/vis';
|
||||||
|
|
||||||
var $visCanvas = $('<div>')
|
let $visCanvas = $('<div>')
|
||||||
.attr('id', 'vislib-vis-fixtures')
|
.attr('id', 'vislib-vis-fixtures')
|
||||||
.css({
|
.css({
|
||||||
height: '500px',
|
height: '500px',
|
||||||
|
@ -15,8 +15,8 @@ var $visCanvas = $('<div>')
|
||||||
})
|
})
|
||||||
.appendTo('body');
|
.appendTo('body');
|
||||||
|
|
||||||
var count = 0;
|
let count = 0;
|
||||||
var visHeight = $visCanvas.height();
|
let visHeight = $visCanvas.height();
|
||||||
|
|
||||||
$visCanvas.new = function () {
|
$visCanvas.new = function () {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
@ -32,7 +32,7 @@ afterEach(function () {
|
||||||
|
|
||||||
module.exports = function VislibFixtures(Private) {
|
module.exports = function VislibFixtures(Private) {
|
||||||
return function (visLibParams) {
|
return function (visLibParams) {
|
||||||
var Vis = Private(VislibVisProvider);
|
let Vis = Private(VislibVisProvider);
|
||||||
return new Vis($visCanvas.new(), _.defaults({}, visLibParams || {}, {
|
return new Vis($visCanvas.new(), _.defaults({}, visLibParams || {}, {
|
||||||
shareYAxis: true,
|
shareYAxis: true,
|
||||||
addTooltip: true,
|
addTooltip: true,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
var fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
let fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
||||||
|
|
||||||
if (!process.env.BABEL_CACHE_PATH) {
|
if (!process.env.BABEL_CACHE_PATH) {
|
||||||
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
var cloneDeep = require('lodash').cloneDeep;
|
// this file is not transpiled
|
||||||
var fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
'use strict'; // eslint-disable-line strict
|
||||||
|
|
||||||
|
let cloneDeep = require('lodash').cloneDeep;
|
||||||
|
let fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
||||||
|
|
||||||
if (!process.env.BABEL_CACHE_PATH) {
|
if (!process.env.BABEL_CACHE_PATH) {
|
||||||
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import LazyServer from './lazy_server';
|
import LazyServer from './lazy_server';
|
||||||
import LazyOptimizer from './lazy_optimizer';
|
import LazyOptimizer from './lazy_optimizer';
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
|
|
||||||
export default async (kbnServer, kibanaHapiServer, config) => {
|
export default async (kbnServer, kibanaHapiServer, config) => {
|
||||||
let server = new LazyServer(
|
let server = new LazyServer(
|
||||||
|
|
|
@ -26,7 +26,7 @@ docViewsRegistry.register(function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.showArrayInObjectsWarning = function (row, field) {
|
$scope.showArrayInObjectsWarning = function (row, field) {
|
||||||
var value = $scope.flattened[field];
|
let value = $scope.flattened[field];
|
||||||
return _.isArray(value) && typeof value[0] === 'object';
|
return _.isArray(value) && typeof value[0] === 'object';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import VisSchemasProvider from 'ui/vis/schemas';
|
||||||
import AggResponseGeoJsonGeoJsonProvider from 'ui/agg_response/geo_json/geo_json';
|
import AggResponseGeoJsonGeoJsonProvider from 'ui/agg_response/geo_json/geo_json';
|
||||||
import FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';
|
import FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';
|
||||||
import tileMapTemplate from 'plugins/kbn_vislib_vis_types/editors/tile_map.html';
|
import tileMapTemplate from 'plugins/kbn_vislib_vis_types/editors/tile_map.html';
|
||||||
|
|
||||||
export default function TileMapVisType(Private, getAppState, courier, config) {
|
export default function TileMapVisType(Private, getAppState, courier, config) {
|
||||||
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
|
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
|
||||||
const Schemas = Private(VisSchemasProvider);
|
const Schemas = Private(VisSchemasProvider);
|
||||||
|
@ -120,6 +121,8 @@ export default function TileMapVisType(Private, getAppState, courier, config) {
|
||||||
group: 'buckets',
|
group: 'buckets',
|
||||||
name: 'split',
|
name: 'split',
|
||||||
title: 'Split Chart',
|
title: 'Split Chart',
|
||||||
|
deprecate: true,
|
||||||
|
deprecateMessage: 'The Split Chart feature for Tile Maps has been deprecated.',
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 1
|
max: 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,11 @@
|
||||||
<div dashboard-app class="app-container dashboard-container">
|
<div dashboard-app class="app-container dashboard-container">
|
||||||
<navbar name="dashboard-options" class="kibana-nav-options">
|
<kbn-top-nav name="dashboard" config="topNavMenu">
|
||||||
<div class="kibana-nav-info">
|
<div class="kibana-nav-info">
|
||||||
<span ng-show="dash.id" class="kibana-nav-info-title">
|
<span ng-show="dash.id" class="kibana-nav-info-title">
|
||||||
<span ng-bind="::dash.title"></span>
|
<span ng-bind="::dash.title"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</kbn-top-nav>
|
||||||
<div class="button-group kibana-nav-actions" role="toolbar">
|
|
||||||
<button ng-click="newDashboard()"
|
|
||||||
aria-label="New Dashboard">
|
|
||||||
<span>New</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Save Dashboard"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('save') }}"
|
|
||||||
ng-class="{active: configTemplate.is('save')}"
|
|
||||||
ng-click="configTemplate.toggle('save');">
|
|
||||||
<span>Save</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Load Saved Dashboard"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('load') }}"
|
|
||||||
ng-class="{active: configTemplate.is('load')}"
|
|
||||||
ng-click="configTemplate.toggle('load');">
|
|
||||||
<span>Open</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Share Dashboard"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('share') }}"
|
|
||||||
ng-class="{active: configTemplate.is('share')}"
|
|
||||||
ng-click="configTemplate.toggle('share');">
|
|
||||||
<span>Share</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Add Visualization"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('pickVis') }}"
|
|
||||||
ng-class="{active: configTemplate.is('pickVis')}"
|
|
||||||
ng-click="configTemplate.toggle('pickVis');">
|
|
||||||
<span>Add visualization</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Options"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('options') }}"
|
|
||||||
ng-class="{active: configTemplate.is('options')}"
|
|
||||||
ng-click="configTemplate.toggle('options');">
|
|
||||||
<span>Options</span>
|
|
||||||
</button>
|
|
||||||
<div class="chrome-actions"kbn-chrome-append-nav-controls></div>
|
|
||||||
</div>
|
|
||||||
</navbar>
|
|
||||||
<config config-template="configTemplate" config-object="opts"></config>
|
|
||||||
|
|
||||||
<navbar ng-show="chrome.getVisible()" name="dashboard-search">
|
<navbar ng-show="chrome.getVisible()" name="dashboard-search">
|
||||||
<form name="queryInput"
|
<form name="queryInput"
|
||||||
|
@ -91,7 +42,7 @@
|
||||||
|
|
||||||
<div ng-show="!state.panels.length" class="text-center start-screen">
|
<div ng-show="!state.panels.length" class="text-center start-screen">
|
||||||
<h2>Ready to get started?</h2>
|
<h2>Ready to get started?</h2>
|
||||||
<p>Click the <a class="btn btn-xs navbtn-inverse" ng-click="configTemplate.open('pickVis'); toggleAddVisualization = !toggleAddVisualization" aria-label="Add visualization"><i aria-hidden="true" class="fa fa-plus-circle"></i></a> button in the menu bar above to add a visualization to the dashboard. <br/>If you haven't setup a visualization yet visit the <a href="#/visualize" title="Visualize">"Visualize"</a> tab to create your first visualization.</p>
|
<p>Click the <a class="btn btn-xs navbtn-inverse" ng-click="kbnTopNav.open('add'); toggleAddVisualization = !toggleAddVisualization" aria-label="Add visualization">Add</a> button in the menu bar above to add a visualization to the dashboard. <br/>If you haven't setup a visualization yet visit the <a href="#/visualize" title="Visualize">"Visualize"</a> tab to create your first visualization.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dashboard-grid></dashboard-grid>
|
<dashboard-grid></dashboard-grid>
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import ConfigTemplate from 'ui/config_template';
|
|
||||||
import chrome from 'ui/chrome';
|
import chrome from 'ui/chrome';
|
||||||
import 'ui/directives/config';
|
import 'ui/directives/kbn_top_nav';
|
||||||
import 'ui/courier';
|
import 'ui/courier';
|
||||||
import 'ui/config';
|
import 'ui/config';
|
||||||
import 'ui/notify';
|
import 'ui/notify';
|
||||||
import 'ui/typeahead';
|
import 'ui/typeahead';
|
||||||
import 'ui/navbar';
|
import 'ui/navbar_extensions';
|
||||||
import 'ui/share';
|
import 'ui/share';
|
||||||
import 'plugins/kibana/dashboard/directives/grid';
|
import 'plugins/kibana/dashboard/directives/grid';
|
||||||
import 'plugins/kibana/dashboard/components/panel/panel';
|
import 'plugins/kibana/dashboard/components/panel/panel';
|
||||||
|
@ -100,15 +99,27 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
||||||
});
|
});
|
||||||
$scope.$watch('state.options.darkTheme', setDarkTheme);
|
$scope.$watch('state.options.darkTheme', setDarkTheme);
|
||||||
|
|
||||||
$scope.configTemplate = new ConfigTemplate({
|
$scope.topNavMenu = [{
|
||||||
save: require('plugins/kibana/dashboard/partials/save_dashboard.html'),
|
key: 'new',
|
||||||
load: require('plugins/kibana/dashboard/partials/load_dashboard.html'),
|
description: 'New Dashboard',
|
||||||
share: require('plugins/kibana/dashboard/partials/share.html'),
|
run: function () { kbnUrl.change('/dashboard', {}); },
|
||||||
pickVis: require('plugins/kibana/dashboard/partials/pick_visualization.html'),
|
}, {
|
||||||
options: require('plugins/kibana/dashboard/partials/options.html'),
|
key: 'add',
|
||||||
filter: require('ui/chrome/config/filter.html'),
|
description: 'Add a panel to the dashboard',
|
||||||
interval: require('ui/chrome/config/interval.html')
|
template: require('plugins/kibana/dashboard/partials/pick_visualization.html')
|
||||||
});
|
}, {
|
||||||
|
key: 'save',
|
||||||
|
description: 'Save Dashboard',
|
||||||
|
template: require('plugins/kibana/dashboard/partials/save_dashboard.html')
|
||||||
|
}, {
|
||||||
|
key: 'open',
|
||||||
|
description: 'Load Saved Dashboard',
|
||||||
|
template: require('plugins/kibana/dashboard/partials/load_dashboard.html')
|
||||||
|
}, {
|
||||||
|
key: 'share',
|
||||||
|
description: 'Share Dashboard',
|
||||||
|
template: require('plugins/kibana/dashboard/partials/share.html')
|
||||||
|
}];
|
||||||
|
|
||||||
$scope.refresh = _.bindKey(courier, 'fetch');
|
$scope.refresh = _.bindKey(courier, 'fetch');
|
||||||
|
|
||||||
|
@ -198,7 +209,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
||||||
|
|
||||||
dash.save()
|
dash.save()
|
||||||
.then(function (id) {
|
.then(function (id) {
|
||||||
$scope.configTemplate.close('save');
|
$scope.kbnTopNav.close('save');
|
||||||
if (id) {
|
if (id) {
|
||||||
notify.info('Saved Dashboard as "' + dash.title + '"');
|
notify.info('Saved Dashboard as "' + dash.title + '"');
|
||||||
if (dash.id !== $routeParams.id) {
|
if (dash.id !== $routeParams.id) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import ConfigTemplate from 'ui/config_template';
|
|
||||||
import getSort from 'ui/doc_table/lib/get_sort';
|
import getSort from 'ui/doc_table/lib/get_sort';
|
||||||
import rison from 'ui/utils/rison';
|
import rison from 'ui/utils/rison';
|
||||||
import dateMath from 'ui/utils/date_math';
|
import dateMath from 'ui/utils/date_math';
|
||||||
|
@ -96,14 +95,23 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
||||||
$scope.toggleInterval = function () {
|
$scope.toggleInterval = function () {
|
||||||
$scope.showInterval = !$scope.showInterval;
|
$scope.showInterval = !$scope.showInterval;
|
||||||
};
|
};
|
||||||
// config panel templates
|
$scope.topNavMenu = [{
|
||||||
$scope.configTemplate = new ConfigTemplate({
|
key: 'new',
|
||||||
load: require('plugins/kibana/discover/partials/load_search.html'),
|
description: 'New Search',
|
||||||
save: require('plugins/kibana/discover/partials/save_search.html'),
|
run: function () { kbnUrl.change('/discover'); }
|
||||||
share: require('plugins/kibana/discover/partials/share_search.html'),
|
}, {
|
||||||
filter: require('ui/chrome/config/filter.html'),
|
key: 'save',
|
||||||
interval: require('ui/chrome/config/interval.html')
|
description: 'Save Search',
|
||||||
});
|
template: require('plugins/kibana/discover/partials/save_search.html')
|
||||||
|
}, {
|
||||||
|
key: 'open',
|
||||||
|
description: 'Load Saved Search',
|
||||||
|
template: require('plugins/kibana/discover/partials/load_search.html')
|
||||||
|
}, {
|
||||||
|
key: 'share',
|
||||||
|
description: 'Share Search',
|
||||||
|
template: require('plugins/kibana/discover/partials/share_search.html')
|
||||||
|
}];
|
||||||
$scope.timefilter = timefilter;
|
$scope.timefilter = timefilter;
|
||||||
|
|
||||||
|
|
||||||
|
@ -287,7 +295,7 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
||||||
|
|
||||||
return savedSearch.save()
|
return savedSearch.save()
|
||||||
.then(function (id) {
|
.then(function (id) {
|
||||||
$scope.configTemplate.close('save');
|
$scope.kbnTopNav.close('save');
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div ng-controller="discover" class="app-container">
|
<div ng-controller="discover" class="app-container">
|
||||||
<navbar name="discover-options" class="kibana-nav-options">
|
<kbn-top-nav name="discover" config="topNavMenu">
|
||||||
<div class="kibana-nav-info">
|
<div class="kibana-nav-info">
|
||||||
<span ng-show="opts.savedSearch.id" class="kibana-nav-info-title">
|
<span ng-show="opts.savedSearch.id" class="kibana-nav-info-title">
|
||||||
<span ng-bind="::opts.savedSearch.title"></span>
|
<span ng-bind="::opts.savedSearch.title"></span>
|
||||||
|
@ -9,44 +9,7 @@
|
||||||
<strong class="discover-info-hits">{{(hits || 0) | number:0}}</strong>
|
<strong class="discover-info-hits">{{(hits || 0) | number:0}}</strong>
|
||||||
<ng-pluralize count="hits" when="{'1':'hit', 'other':'hits'}"></ng-pluralize>
|
<ng-pluralize count="hits" when="{'1':'hit', 'other':'hits'}"></ng-pluralize>
|
||||||
</div>
|
</div>
|
||||||
<div class="kibana-nav-actions button-group" role="toolbar">
|
</kbn-top-nav>
|
||||||
<button
|
|
||||||
ng-click="newQuery()"
|
|
||||||
aria-label="New Search">
|
|
||||||
<span>New</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ng-click="configTemplate.toggle('save');"
|
|
||||||
ng-class="{active: configTemplate.is('save')}"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('save') }}"
|
|
||||||
aria-label="Save Search">
|
|
||||||
<span>Save</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('load') }}"
|
|
||||||
ng-click="configTemplate.toggle('load');"
|
|
||||||
ng-class="{active: configTemplate.is('load')}"
|
|
||||||
aria-label="Load Saved Search">
|
|
||||||
<span>Open</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
aria-label="Share Search"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('share') }}"
|
|
||||||
ng-class="{active: configTemplate.is('share')}"
|
|
||||||
ng-click="configTemplate.toggle('share');">
|
|
||||||
<span>Share</span>
|
|
||||||
</button>
|
|
||||||
<div class="chrome-actions" kbn-chrome-append-nav-controls></div>
|
|
||||||
</div>
|
|
||||||
</navbar>
|
|
||||||
<config
|
|
||||||
config-template="configTemplate"
|
|
||||||
config-object="opts"
|
|
||||||
config-close="configClose"
|
|
||||||
></config>
|
|
||||||
<navbar name="discover-search">
|
<navbar name="discover-search">
|
||||||
<form role="form" class="fill inline-form" ng-submit="fetch()" name="discoverSearch">
|
<form role="form" class="fill inline-form" ng-submit="fetch()" name="discoverSearch">
|
||||||
<div class="typeahead" kbn-typeahead="discover">
|
<div class="typeahead" kbn-typeahead="discover">
|
||||||
|
@ -116,7 +79,7 @@
|
||||||
<div ng-show="opts.timefield">
|
<div ng-show="opts.timefield">
|
||||||
<p>
|
<p>
|
||||||
<h3>Expand your time range</h3>
|
<h3>Expand your time range</h3>
|
||||||
<p>I see you are looking at an index with a date field. It is possible your query does not match anything in the current time range, or that there is no data at all in the currently selected time range. Click the button below to open the time picker. For future reference you can open the time picker by clicking the <a class="btn btn-xs navbtn" ng-click="toggleTimepicker(); toggledTimepicker = !toggledTimepicker" aria-expanded="{{toggledTimepicker}}" aria-label="time picker">time picker <i aria-hidden="true" class="fa fa-clock-o"></i></a> in the top right corner of your screen.
|
<p>I see you are looking at an index with a date field. It is possible your query does not match anything in the current time range, or that there is no data at all in the currently selected time range. Try selecting a wider time range by opening the time picker <i aria-hidden="true" class="fa fa-clock-o"></i> in the top right corner of your screen.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'plugins/kibana/discover/saved_searches/saved_searches';
|
import 'plugins/kibana/discover/saved_searches/saved_searches';
|
||||||
import 'plugins/kibana/discover/directives/timechart';
|
import 'plugins/kibana/discover/directives/timechart';
|
||||||
import 'ui/navbar';
|
import 'ui/navbar_extensions';
|
||||||
import 'ui/collapsible_sidebar';
|
import 'ui/collapsible_sidebar';
|
||||||
import 'plugins/kibana/discover/components/field_chooser/field_chooser';
|
import 'plugins/kibana/discover/components/field_chooser/field_chooser';
|
||||||
import 'plugins/kibana/discover/controllers/discover';
|
import 'plugins/kibana/discover/controllers/discover';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<input id="SaveSearch" ng-model="opts.savedSearch.title" input-focus="select" class="form-control" placeholder="Name this search...">
|
<input id="SaveSearch" ng-model="opts.savedSearch.title" input-focus="select" class="form-control" placeholder="Name this search...">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button ng-disabled="!opts.savedSearch.title" type="submit" class="btn btn-primary">
|
<button ng-disabled="!opts.savedSearch.title" data-test-subj="discover-save-search-btn" type="submit" class="btn btn-primary">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,12 +40,11 @@ uiModules
|
||||||
},
|
},
|
||||||
link: function ($scope, $el) {
|
link: function ($scope, $el) {
|
||||||
timefilter.enabled = false;
|
timefilter.enabled = false;
|
||||||
$scope.sections = sections;
|
|
||||||
$scope.sections = sections.inOrder;
|
$scope.sections = sections.inOrder;
|
||||||
$scope.section = _.find($scope.sections, { name: $scope.sectionName });
|
$scope.section = _.find($scope.sections, { name: $scope.sectionName });
|
||||||
|
|
||||||
$scope.sections.forEach(function (section) {
|
$scope.sections.forEach(section => {
|
||||||
section.class = (section === $scope.section) ? 'active' : void 0;
|
section.class = section === $scope.section ? 'active' : undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<label>Select {{ groupName }} type</label>
|
<label>Select {{ groupName }} type</label>
|
||||||
<ul class="form-group list-group list-group-menu">
|
<ul class="form-group list-group list-group-menu">
|
||||||
<li
|
<li
|
||||||
|
ng-hide="schema.deprecate"
|
||||||
ng-repeat="schema in availableSchema"
|
ng-repeat="schema in availableSchema"
|
||||||
ng-click="add.submit(schema)"
|
ng-click="add.submit(schema)"
|
||||||
class="list-group-item list-group-menu-item">
|
class="list-group-item list-group-menu-item">
|
||||||
|
@ -18,10 +19,10 @@
|
||||||
class="vis-editor-agg-wide-btn">
|
class="vis-editor-agg-wide-btn">
|
||||||
|
|
||||||
<div ng-if="!add.form">
|
<div ng-if="!add.form">
|
||||||
<div class="btn btn-sm btn-primary" ng-if="groupName !== 'buckets' || !stats.count">
|
<div class="btn btn-sm btn-primary" ng-if="groupName !== 'buckets' || !stats.count && !stats.deprecate">
|
||||||
Add {{ groupName }}
|
Add {{ groupName }}
|
||||||
</div>
|
</div>
|
||||||
<div class="btn btn-sm btn-primary" ng-if="groupName === 'buckets' && stats.count > 0">
|
<div class="btn btn-sm btn-primary" ng-if="groupName === 'buckets' && stats.count > 0 && !stats.deprecate">
|
||||||
Add sub-{{ groupName }}
|
Add sub-{{ groupName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,6 +33,7 @@ uiModules
|
||||||
$scope.schemas.forEach(function (schema) {
|
$scope.schemas.forEach(function (schema) {
|
||||||
stats.min += schema.min;
|
stats.min += schema.min;
|
||||||
stats.max += schema.max;
|
stats.max += schema.max;
|
||||||
|
stats.deprecate = schema.deprecate;
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.availableSchema = $scope.schemas.filter(function (schema) {
|
$scope.availableSchema = $scope.schemas.filter(function (schema) {
|
||||||
|
|
|
@ -10,4 +10,13 @@
|
||||||
style="display: none;">
|
style="display: none;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="agg.schema.deprecate" class="form-group">
|
||||||
|
<p ng-show="agg.schema.deprecateMessage" class="vis-editor-agg-error">
|
||||||
|
{{ agg.schema.deprecateMessage }}
|
||||||
|
</p>
|
||||||
|
<p ng-show="!agg.schema.deprecateMessage" class="vis-editor-agg-error">
|
||||||
|
"{{ agg.schema.title }}" has been deprecated.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- schema editors get added down here: aggSelect.html, agg_types/controls/*.html -->
|
<!-- schema editors get added down here: aggSelect.html, agg_types/controls/*.html -->
|
|
@ -1,60 +1,12 @@
|
||||||
<div ng-controller="VisEditor" class="app-container vis-editor vis-type-{{ vis.type.name }}">
|
<div ng-controller="VisEditor" class="app-container vis-editor vis-type-{{ vis.type.name }}">
|
||||||
<navbar name="visualize-options" class="kibana-nav-options" ng-if="chrome.getVisible()">
|
|
||||||
|
<kbn-top-nav name="visualize" config="topNavMenu">
|
||||||
<div class="vis-editor-info">
|
<div class="vis-editor-info">
|
||||||
<span ng-show="savedVis.id" class="vis-editor-info-title">
|
<span ng-show="savedVis.id" class="vis-editor-info-title">
|
||||||
<span ng-bind="::savedVis.title"></span>
|
<span ng-bind="::savedVis.title"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</kbn-top-nav>
|
||||||
<div class="button-group kibana-nav-actions">
|
|
||||||
<button ng-click="startOver()" aria-label="New Visualization">
|
|
||||||
<span>New</span>
|
|
||||||
</button>
|
|
||||||
<!-- normal save -->
|
|
||||||
<button
|
|
||||||
ng-class="{active: configTemplate.is('save')}"
|
|
||||||
ng-click="configTemplate.toggle('save')"
|
|
||||||
ng-if="!editableVis.dirty"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('save') }}"
|
|
||||||
aria-label="Save Visualization">
|
|
||||||
<span>Save</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- save stub with tooltip -->
|
|
||||||
<button disabled ng-if="editableVis.dirty" tooltip="Apply or Discard your changes before saving" aria-label="Apply or Discard your changes before saving">
|
|
||||||
<span>Save</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ng-class="{active: configTemplate.is('load')}"
|
|
||||||
ng-click="configTemplate.toggle('load')"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('load') }}"
|
|
||||||
aria-label="Load Saved Visualization">
|
|
||||||
<span>Load</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ng-class="{active: configTemplate.is('share')}"
|
|
||||||
ng-click="configTemplate.toggle('share')"
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded="{{ configTemplate.is('share') }}"
|
|
||||||
aria-label="Share Visualization">
|
|
||||||
<span>Share</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ng-click="fetch()"
|
|
||||||
aria-label="Refresh">
|
|
||||||
<span>Refresh</span>
|
|
||||||
</button>
|
|
||||||
<div class="chrome-actions"kbn-chrome-append-nav-controls></div>
|
|
||||||
</div>
|
|
||||||
</navbar>
|
|
||||||
<config
|
|
||||||
ng-if="chrome.getVisible()"
|
|
||||||
config-template="configTemplate"
|
|
||||||
config-object="opts">
|
|
||||||
</config>
|
|
||||||
|
|
||||||
<navbar ng-if="chrome.getVisible()" name="visualize-search">
|
<navbar ng-if="chrome.getVisible()" name="visualize-search">
|
||||||
<div class="fill bitty-modal-container">
|
<div class="fill bitty-modal-container">
|
||||||
<div ng-if="vis.type.requiresSearch && $state.linked && !unlinking"
|
<div ng-if="vis.type.requiresSearch && $state.linked && !unlinking"
|
||||||
|
|
|
@ -2,12 +2,11 @@ import _ from 'lodash';
|
||||||
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
|
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
|
||||||
import 'plugins/kibana/visualize/editor/sidebar';
|
import 'plugins/kibana/visualize/editor/sidebar';
|
||||||
import 'plugins/kibana/visualize/editor/agg_filter';
|
import 'plugins/kibana/visualize/editor/agg_filter';
|
||||||
import 'ui/navbar';
|
import 'ui/navbar_extensions';
|
||||||
import 'ui/visualize';
|
import 'ui/visualize';
|
||||||
import 'ui/collapsible_sidebar';
|
import 'ui/collapsible_sidebar';
|
||||||
import 'ui/share';
|
import 'ui/share';
|
||||||
import angular from 'angular';
|
import angular from 'angular';
|
||||||
import ConfigTemplate from 'ui/config_template';
|
|
||||||
import Notifier from 'ui/notify/notifier';
|
import Notifier from 'ui/notify/notifier';
|
||||||
import RegistryVisTypesProvider from 'ui/registry/vis_types';
|
import RegistryVisTypesProvider from 'ui/registry/vis_types';
|
||||||
import DocTitleProvider from 'ui/doc_title';
|
import DocTitleProvider from 'ui/doc_title';
|
||||||
|
@ -80,14 +79,27 @@ uiModules
|
||||||
|
|
||||||
const searchSource = savedVis.searchSource;
|
const searchSource = savedVis.searchSource;
|
||||||
|
|
||||||
// config panel templates
|
$scope.topNavMenu = [{
|
||||||
const configTemplate = new ConfigTemplate({
|
key: 'new',
|
||||||
save: require('plugins/kibana/visualize/editor/panels/save.html'),
|
description: 'New Visualization',
|
||||||
load: require('plugins/kibana/visualize/editor/panels/load.html'),
|
run: function () { kbnUrl.change('/visualize', {}); }
|
||||||
share: require('plugins/kibana/visualize/editor/panels/share.html'),
|
}, {
|
||||||
filter: require('ui/chrome/config/filter.html'),
|
key: 'save',
|
||||||
interval: require('ui/chrome/config/interval.html')
|
template: require('plugins/kibana/visualize/editor/panels/save.html'),
|
||||||
});
|
description: 'Save Visualization'
|
||||||
|
}, {
|
||||||
|
key: 'load',
|
||||||
|
template: require('plugins/kibana/visualize/editor/panels/load.html'),
|
||||||
|
description: 'Load Saved Visualization',
|
||||||
|
}, {
|
||||||
|
key: 'share',
|
||||||
|
template: require('plugins/kibana/visualize/editor/panels/share.html'),
|
||||||
|
description: 'Share Visualization'
|
||||||
|
}, {
|
||||||
|
key: 'refresh',
|
||||||
|
description: 'Refresh',
|
||||||
|
run: function () { $scope.fetch(); }
|
||||||
|
}];
|
||||||
|
|
||||||
if (savedVis.id) {
|
if (savedVis.id) {
|
||||||
docTitle.change(savedVis.title);
|
docTitle.change(savedVis.title);
|
||||||
|
@ -129,7 +141,6 @@ uiModules
|
||||||
$scope.uiState = $state.makeStateful('uiState');
|
$scope.uiState = $state.makeStateful('uiState');
|
||||||
$scope.timefilter = timefilter;
|
$scope.timefilter = timefilter;
|
||||||
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter');
|
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter');
|
||||||
$scope.configTemplate = configTemplate;
|
|
||||||
|
|
||||||
editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state);
|
editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state);
|
||||||
editableVis.listeners.brush = vis.listeners.brush = brushEvent;
|
editableVis.listeners.brush = vis.listeners.brush = brushEvent;
|
||||||
|
@ -235,7 +246,7 @@ uiModules
|
||||||
|
|
||||||
savedVis.save()
|
savedVis.save()
|
||||||
.then(function (id) {
|
.then(function (id) {
|
||||||
configTemplate.close('save');
|
$scope.kbnTopNav.close('save');
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
notify.info('Saved Visualization "' + savedVis.title + '"');
|
notify.info('Saved Visualization "' + savedVis.title + '"');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
import { chain, memoize } from 'lodash';
|
import { chain, memoize } from 'lodash';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { map, fromNode } from 'bluebird';
|
import { map, fromNode } from 'bluebird';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
|
|
||||||
import findSourceFiles from './find_source_files';
|
import findSourceFiles from './find_source_files';
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
|
|
||||||
export default (kibana) => {
|
export default (kibana) => {
|
||||||
return new kibana.Plugin({
|
return new kibana.Plugin({
|
||||||
|
|
|
@ -9,12 +9,12 @@ import Joi from 'joi';
|
||||||
*
|
*
|
||||||
* Config should be newed up with a joi schema (containing defaults via joi)
|
* Config should be newed up with a joi schema (containing defaults via joi)
|
||||||
*
|
*
|
||||||
* var schema = { ... }
|
* let schema = { ... }
|
||||||
* new Config(schema);
|
* new Config(schema);
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var data = {
|
let data = {
|
||||||
test: {
|
test: {
|
||||||
hosts: ['host-01', 'host-02'],
|
hosts: ['host-01', 'host-02'],
|
||||||
client: {
|
client: {
|
||||||
|
@ -25,7 +25,7 @@ var data = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var schema = Joi.object({
|
let schema = Joi.object({
|
||||||
test: Joi.object({
|
test: Joi.object({
|
||||||
enable: Joi.boolean().default(true),
|
enable: Joi.boolean().default(true),
|
||||||
hosts: Joi.array().items(Joi.string()),
|
hosts: Joi.array().items(Joi.string()),
|
||||||
|
@ -44,39 +44,39 @@ describe('lib/config/config', function () {
|
||||||
describe('constructor', function () {
|
describe('constructor', function () {
|
||||||
|
|
||||||
it('should not allow any config if the schema is not passed', function () {
|
it('should not allow any config if the schema is not passed', function () {
|
||||||
var config = new Config();
|
let config = new Config();
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.set('something.enable', true);
|
config.set('something.enable', true);
|
||||||
};
|
};
|
||||||
expect(run).to.throwException();
|
expect(run).to.throwException();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow keys in the schema', function () {
|
it('should allow keys in the schema', function () {
|
||||||
var config = new Config(schema);
|
let config = new Config(schema);
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.set('test.client.host', 'http://0.0.0.0');
|
config.set('test.client.host', 'http://0.0.0.0');
|
||||||
};
|
};
|
||||||
expect(run).to.not.throwException();
|
expect(run).to.not.throwException();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow keys not in the schema', function () {
|
it('should not allow keys not in the schema', function () {
|
||||||
var config = new Config(schema);
|
let config = new Config(schema);
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.set('paramNotDefinedInTheSchema', true);
|
config.set('paramNotDefinedInTheSchema', true);
|
||||||
};
|
};
|
||||||
expect(run).to.throwException();
|
expect(run).to.throwException();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow child keys not in the schema', function () {
|
it('should not allow child keys not in the schema', function () {
|
||||||
var config = new Config(schema);
|
let config = new Config(schema);
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.set('test.client.paramNotDefinedInTheSchema', true);
|
config.set('test.client.paramNotDefinedInTheSchema', true);
|
||||||
};
|
};
|
||||||
expect(run).to.throwException();
|
expect(run).to.throwException();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set defaults', function () {
|
it('should set defaults', function () {
|
||||||
var config = new Config(schema);
|
let config = new Config(schema);
|
||||||
expect(config.get('test.enable')).to.be(true);
|
expect(config.get('test.enable')).to.be(true);
|
||||||
expect(config.get('test.client.type')).to.be('datastore');
|
expect(config.get('test.client.type')).to.be('datastore');
|
||||||
});
|
});
|
||||||
|
@ -92,7 +92,7 @@ describe('lib/config/config', function () {
|
||||||
|
|
||||||
it('should reset the config object with new values', function () {
|
it('should reset the config object with new values', function () {
|
||||||
config.set(data);
|
config.set(data);
|
||||||
var newData = config.get();
|
let newData = config.get();
|
||||||
newData.test.enable = false;
|
newData.test.enable = false;
|
||||||
config.resetTo(newData);
|
config.resetTo(newData);
|
||||||
expect(config.get()).to.eql(newData);
|
expect(config.get()).to.eql(newData);
|
||||||
|
@ -134,21 +134,21 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use an object to set config values', function () {
|
it('should use an object to set config values', function () {
|
||||||
var hosts = ['host-01', 'host-02'];
|
let hosts = ['host-01', 'host-02'];
|
||||||
config.set({ test: { enable: false, hosts: hosts } });
|
config.set({ test: { enable: false, hosts: hosts } });
|
||||||
expect(config.get('test.enable')).to.be(false);
|
expect(config.get('test.enable')).to.be(false);
|
||||||
expect(config.get('test.hosts')).to.eql(hosts);
|
expect(config.get('test.hosts')).to.eql(hosts);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use a flatten object to set config values', function () {
|
it('should use a flatten object to set config values', function () {
|
||||||
var hosts = ['host-01', 'host-02'];
|
let hosts = ['host-01', 'host-02'];
|
||||||
config.set({ 'test.enable': false, 'test.hosts': hosts });
|
config.set({ 'test.enable': false, 'test.hosts': hosts });
|
||||||
expect(config.get('test.enable')).to.be(false);
|
expect(config.get('test.enable')).to.be(false);
|
||||||
expect(config.get('test.hosts')).to.eql(hosts);
|
expect(config.get('test.hosts')).to.eql(hosts);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should override values with just the values present', function () {
|
it('should override values with just the values present', function () {
|
||||||
var newData = _.cloneDeep(data);
|
let newData = _.cloneDeep(data);
|
||||||
config.set(data);
|
config.set(data);
|
||||||
newData.test.enable = false;
|
newData.test.enable = false;
|
||||||
config.set({ test: { enable: false } });
|
config.set({ test: { enable: false } });
|
||||||
|
@ -156,7 +156,7 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should thow an exception when setting a value with the wrong type', function (done) {
|
it('should thow an exception when setting a value with the wrong type', function (done) {
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.set('test.enable', 'something');
|
config.set('test.enable', 'something');
|
||||||
};
|
};
|
||||||
expect(run).to.throwException(function (err) {
|
expect(run).to.throwException(function (err) {
|
||||||
|
@ -179,7 +179,7 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the whole config object when called without a key', function () {
|
it('should return the whole config object when called without a key', function () {
|
||||||
var newData = _.cloneDeep(data);
|
let newData = _.cloneDeep(data);
|
||||||
newData.test.enable = true;
|
newData.test.enable = true;
|
||||||
expect(config.get()).to.eql(newData);
|
expect(config.get()).to.eql(newData);
|
||||||
});
|
});
|
||||||
|
@ -194,14 +194,14 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw exception for unknown config values', function () {
|
it('should throw exception for unknown config values', function () {
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.get('test.does.not.exist');
|
config.get('test.does.not.exist');
|
||||||
};
|
};
|
||||||
expect(run).to.throwException(/Unknown config key: test.does.not.exist/);
|
expect(run).to.throwException(/Unknown config key: test.does.not.exist/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw exception for undefined known config values', function () {
|
it('should not throw exception for undefined known config values', function () {
|
||||||
var run = function getUndefValue() {
|
let run = function getUndefValue() {
|
||||||
config.get('test.undefValue');
|
config.get('test.undefValue');
|
||||||
};
|
};
|
||||||
expect(run).to.not.throwException();
|
expect(run).to.not.throwException();
|
||||||
|
@ -216,13 +216,13 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow you to extend the schema at the top level', function () {
|
it('should allow you to extend the schema at the top level', function () {
|
||||||
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
let newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||||
config.extendSchema('myTest', newSchema);
|
config.extendSchema('myTest', newSchema);
|
||||||
expect(config.get('myTest.test')).to.be(true);
|
expect(config.get('myTest.test')).to.be(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow you to extend the schema with a prefix', function () {
|
it('should allow you to extend the schema with a prefix', function () {
|
||||||
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
let newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||||
config.extendSchema('prefix.myTest', newSchema);
|
config.extendSchema('prefix.myTest', newSchema);
|
||||||
expect(config.get('prefix')).to.eql({ myTest: { test: true }});
|
expect(config.get('prefix')).to.eql({ myTest: { test: true }});
|
||||||
expect(config.get('prefix.myTest')).to.eql({ test: true });
|
expect(config.get('prefix.myTest')).to.eql({ test: true });
|
||||||
|
@ -230,8 +230,8 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should NOT allow you to extend the schema if somethign else is there', function () {
|
it('should NOT allow you to extend the schema if somethign else is there', function () {
|
||||||
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
let newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||||
var run = function () {
|
let run = function () {
|
||||||
config.extendSchema('test', newSchema);
|
config.extendSchema('test', newSchema);
|
||||||
};
|
};
|
||||||
expect(run).to.throwException();
|
expect(run).to.throwException();
|
||||||
|
@ -241,7 +241,7 @@ describe('lib/config/config', function () {
|
||||||
|
|
||||||
describe('#removeSchema(key)', function () {
|
describe('#removeSchema(key)', function () {
|
||||||
it('should completely remove the key', function () {
|
it('should completely remove the key', function () {
|
||||||
var config = new Config(Joi.object().keys({
|
let config = new Config(Joi.object().keys({
|
||||||
a: Joi.number().default(1)
|
a: Joi.number().default(1)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ describe('lib/config/config', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only removes existing keys', function () {
|
it('only removes existing keys', function () {
|
||||||
var config = new Config(Joi.object());
|
let config = new Config(Joi.object());
|
||||||
|
|
||||||
expect(() => config.removeSchema('b')).to.throwException('Unknown schema');
|
expect(() => config.removeSchema('b')).to.throwException('Unknown schema');
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import expect from 'expect.js';
|
||||||
describe('explode_by(dot, flatObject)', function () {
|
describe('explode_by(dot, flatObject)', function () {
|
||||||
|
|
||||||
it('should explode a flatten object with dots', function () {
|
it('should explode a flatten object with dots', function () {
|
||||||
var flatObject = {
|
let flatObject = {
|
||||||
'test.enable': true,
|
'test.enable': true,
|
||||||
'test.hosts': ['host-01', 'host-02']
|
'test.hosts': ['host-01', 'host-02']
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ describe('explode_by(dot, flatObject)', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should explode a flatten object with slashes', function () {
|
it('should explode a flatten object with slashes', function () {
|
||||||
var flatObject = {
|
let flatObject = {
|
||||||
'test/enable': true,
|
'test/enable': true,
|
||||||
'test/hosts': ['host-01', 'host-02']
|
'test/hosts': ['host-01', 'host-02']
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import expect from 'expect.js';
|
||||||
describe('flatten_with(dot, nestedObj)', function () {
|
describe('flatten_with(dot, nestedObj)', function () {
|
||||||
|
|
||||||
it('should flatten object with dot', function () {
|
it('should flatten object with dot', function () {
|
||||||
var nestedObj = {
|
let nestedObj = {
|
||||||
test: {
|
test: {
|
||||||
enable: true,
|
enable: true,
|
||||||
hosts: ['host-01', 'host-02'],
|
hosts: ['host-01', 'host-02'],
|
||||||
|
|
|
@ -4,7 +4,7 @@ import expect from 'expect.js';
|
||||||
describe('override(target, source)', function () {
|
describe('override(target, source)', function () {
|
||||||
|
|
||||||
it('should override the values form source to target', function () {
|
it('should override the values form source to target', function () {
|
||||||
var target = {
|
let target = {
|
||||||
test: {
|
test: {
|
||||||
enable: true,
|
enable: true,
|
||||||
host: ['host-01', 'host-02'],
|
host: ['host-01', 'host-02'],
|
||||||
|
@ -13,7 +13,7 @@ describe('override(target, source)', function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var source = { test: { client: { type: 'nosql' } } };
|
let source = { test: { client: { type: 'nosql' } } };
|
||||||
expect(override(target, source)).to.eql({
|
expect(override(target, source)).to.eql({
|
||||||
test: {
|
test: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
module.exports = function (dot, flatObject) {
|
module.exports = function (dot, flatObject) {
|
||||||
var fullObject = {};
|
let fullObject = {};
|
||||||
_.each(flatObject, function (value, key) {
|
_.each(flatObject, function (value, key) {
|
||||||
var keys = key.split(dot);
|
let keys = key.split(dot);
|
||||||
(function walk(memo, keys, value) {
|
(function walk(memo, keys, value) {
|
||||||
var _key = keys.shift();
|
let _key = keys.shift();
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
memo[_key] = value;
|
memo[_key] = value;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
module.exports = function (dot, nestedObj, flattenArrays) {
|
module.exports = function (dot, nestedObj, flattenArrays) {
|
||||||
let key; // original key
|
let key; // original key
|
||||||
var stack = []; // track key stack
|
let stack = []; // track key stack
|
||||||
var flatObj = {};
|
let flatObj = {};
|
||||||
(function flattenObj(obj) {
|
(function flattenObj(obj) {
|
||||||
_.keys(obj).forEach(function (key) {
|
_.keys(obj).forEach(function (key) {
|
||||||
stack.push(key);
|
stack.push(key);
|
||||||
|
|
|
@ -3,8 +3,8 @@ import flattenWith from './flatten_with';
|
||||||
import explodeBy from './explode_by';
|
import explodeBy from './explode_by';
|
||||||
|
|
||||||
module.exports = function (target, source) {
|
module.exports = function (target, source) {
|
||||||
var _target = flattenWith('.', target);
|
let _target = flattenWith('.', target);
|
||||||
var _source = flattenWith('.', source);
|
let _source = flattenWith('.', source);
|
||||||
return explodeBy('.', _.defaults(_source, _target));
|
return explodeBy('.', _.defaults(_source, _target));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { get } from 'lodash';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
import fromRoot from '../../utils/from_root';
|
import { fromRoot } from '../../utils';
|
||||||
|
|
||||||
module.exports = () => Joi.object({
|
module.exports = () => Joi.object({
|
||||||
pkg: Joi.object({
|
pkg: Joi.object({
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Hapi from 'hapi';
|
import Hapi from 'hapi';
|
||||||
import { constant, once, compact, flatten } from 'lodash';
|
import { constant, once, compact, flatten } from 'lodash';
|
||||||
import { promisify, resolve, fromNode } from 'bluebird';
|
import { promisify, resolve, fromNode } from 'bluebird';
|
||||||
import fromRoot from '../utils/from_root';
|
import { isWorker } from 'cluster';
|
||||||
import pkg from '../utils/package_json';
|
import { fromRoot, pkg } from '../utils';
|
||||||
|
|
||||||
let rootDir = fromRoot('.');
|
let rootDir = fromRoot('.');
|
||||||
|
|
||||||
|
@ -78,6 +78,11 @@ module.exports = class KbnServer {
|
||||||
await this.ready();
|
await this.ready();
|
||||||
await fromNode(cb => server.start(cb));
|
await fromNode(cb => server.start(cb));
|
||||||
|
|
||||||
|
if (isWorker) {
|
||||||
|
// help parent process know when we are ready
|
||||||
|
process.send(['WORKER_LISTENING']);
|
||||||
|
}
|
||||||
|
|
||||||
server.log(['listening', 'info'], `Server running at ${server.info.uri}`);
|
server.log(['listening', 'info'], `Server running at ${server.info.uri}`);
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import expect from 'expect.js';
|
||||||
|
|
||||||
describe('applyFiltersToKeys(obj, actionsByKey)', function () {
|
describe('applyFiltersToKeys(obj, actionsByKey)', function () {
|
||||||
it('applies for each key+prop in actionsByKey', function () {
|
it('applies for each key+prop in actionsByKey', function () {
|
||||||
var data = applyFiltersToKeys({
|
let data = applyFiltersToKeys({
|
||||||
a: {
|
a: {
|
||||||
b: {
|
b: {
|
||||||
c: 1
|
c: 1
|
||||||
|
|
|
@ -21,7 +21,7 @@ function apply(obj, key, action) {
|
||||||
obj[k] = ('' + val).replace(/./g, 'X');
|
obj[k] = ('' + val).replace(/./g, 'X');
|
||||||
}
|
}
|
||||||
else if (/\/.+\//.test(action)) {
|
else if (/\/.+\//.test(action)) {
|
||||||
var matches = action.match(/\/(.+)\//);
|
let matches = action.match(/\/(.+)\//);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
let regex = new RegExp(matches[1]);
|
let regex = new RegExp(matches[1]);
|
||||||
obj[k] = ('' + val).replace(regex, replacer);
|
obj[k] = ('' + val).replace(regex, replacer);
|
||||||
|
|
|
@ -41,13 +41,13 @@ module.exports = class TransformObjStream extends Stream.Transform {
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform(event, enc, next) {
|
_transform(event, enc, next) {
|
||||||
var data = this.filter(this.readEvent(event));
|
let data = this.filter(this.readEvent(event));
|
||||||
this.push(this.format(data) + '\n');
|
this.push(this.format(data) + '\n');
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
readEvent(event) {
|
readEvent(event) {
|
||||||
var data = {
|
let data = {
|
||||||
type: event.event,
|
type: event.event,
|
||||||
'@timestamp': moment.utc(event.timestamp).format(),
|
'@timestamp': moment.utc(event.timestamp).format(),
|
||||||
tags: [].concat(event.tags || []),
|
tags: [].concat(event.tags || []),
|
||||||
|
@ -69,7 +69,7 @@ module.exports = class TransformObjStream extends Stream.Transform {
|
||||||
referer: event.source.referer
|
referer: event.source.referer
|
||||||
};
|
};
|
||||||
|
|
||||||
var contentLength = 0;
|
let contentLength = 0;
|
||||||
if (typeof event.responsePayload === 'object') {
|
if (typeof event.responsePayload === 'object') {
|
||||||
contentLength = stringify(event.responsePayload).length;
|
contentLength = stringify(event.responsePayload).length;
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,7 +82,7 @@ module.exports = class TransformObjStream extends Stream.Transform {
|
||||||
contentLength: contentLength
|
contentLength: contentLength
|
||||||
};
|
};
|
||||||
|
|
||||||
var query = querystring.stringify(event.query);
|
let query = querystring.stringify(event.query);
|
||||||
if (query) data.req.url += '?' + query;
|
if (query) data.req.url += '?' + query;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,19 @@ import _ from 'lodash';
|
||||||
import Boom from 'boom';
|
import Boom from 'boom';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import { unlinkSync as unlink } from 'fs';
|
import { unlinkSync as unlink } from 'fs';
|
||||||
var writeFile = Promise.promisify(require('fs').writeFile);
|
let writeFile = Promise.promisify(require('fs').writeFile);
|
||||||
|
|
||||||
module.exports = Promise.method(function (kbnServer, server, config) {
|
module.exports = Promise.method(function (kbnServer, server, config) {
|
||||||
var path = config.get('pid.file');
|
let path = config.get('pid.file');
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
|
|
||||||
var pid = String(process.pid);
|
let pid = String(process.pid);
|
||||||
|
|
||||||
return writeFile(path, pid, { flag: 'wx' })
|
return writeFile(path, pid, { flag: 'wx' })
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
if (err.code !== 'EEXIST') throw err;
|
if (err.code !== 'EEXIST') throw err;
|
||||||
|
|
||||||
var log = {
|
let log = {
|
||||||
tmpl: 'pid file already exists at <%= path %>',
|
tmpl: 'pid file already exists at <%= path %>',
|
||||||
path: path,
|
path: path,
|
||||||
pid: pid
|
pid: pid
|
||||||
|
@ -36,7 +36,7 @@ module.exports = Promise.method(function (kbnServer, server, config) {
|
||||||
pid: pid
|
pid: pid
|
||||||
});
|
});
|
||||||
|
|
||||||
var clean = _.once(function (code) {
|
let clean = _.once(function (code) {
|
||||||
unlink(path);
|
unlink(path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { each } from 'bluebird';
|
||||||
import PluginCollection from './plugin_collection';
|
import PluginCollection from './plugin_collection';
|
||||||
module.exports = async (kbnServer, server, config) => {
|
module.exports = async (kbnServer, server, config) => {
|
||||||
|
|
||||||
var plugins = kbnServer.plugins = new PluginCollection(kbnServer);
|
let plugins = kbnServer.plugins = new PluginCollection(kbnServer);
|
||||||
|
|
||||||
let scanDirs = [].concat(config.get('plugins.scanDirs') || []);
|
let scanDirs = [].concat(config.get('plugins.scanDirs') || []);
|
||||||
let pluginPaths = [].concat(config.get('plugins.paths') || []);
|
let pluginPaths = [].concat(config.get('plugins.paths') || []);
|
||||||
|
|
|
@ -17,21 +17,21 @@ describe('ServerStatus class', function () {
|
||||||
|
|
||||||
describe('#create(name)', function () {
|
describe('#create(name)', function () {
|
||||||
it('should create a new status by name', function () {
|
it('should create a new status by name', function () {
|
||||||
var status = serverStatus.create('name');
|
let status = serverStatus.create('name');
|
||||||
expect(status).to.be.a(Status);
|
expect(status).to.be.a(Status);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#get(name)', function () {
|
describe('#get(name)', function () {
|
||||||
it('exposes plugins by name', function () {
|
it('exposes plugins by name', function () {
|
||||||
var status = serverStatus.create('name');
|
let status = serverStatus.create('name');
|
||||||
expect(serverStatus.get('name')).to.be(status);
|
expect(serverStatus.get('name')).to.be(status);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getState(name)', function () {
|
describe('#getState(name)', function () {
|
||||||
it('should expose the state of the plugin by name', function () {
|
it('should expose the state of the plugin by name', function () {
|
||||||
var status = serverStatus.create('name');
|
let status = serverStatus.create('name');
|
||||||
status.green();
|
status.green();
|
||||||
expect(serverStatus.getState('name')).to.be('green');
|
expect(serverStatus.getState('name')).to.be('green');
|
||||||
});
|
});
|
||||||
|
@ -39,11 +39,11 @@ describe('ServerStatus class', function () {
|
||||||
|
|
||||||
describe('#overall()', function () {
|
describe('#overall()', function () {
|
||||||
it('considers each status to produce a summary', function () {
|
it('considers each status to produce a summary', function () {
|
||||||
var status = serverStatus.create('name');
|
let status = serverStatus.create('name');
|
||||||
|
|
||||||
expect(serverStatus.overall().state).to.be('uninitialized');
|
expect(serverStatus.overall().state).to.be('uninitialized');
|
||||||
|
|
||||||
var match = function (overall, state) {
|
let match = function (overall, state) {
|
||||||
expect(overall).to.have.property('state', state.id);
|
expect(overall).to.have.property('state', state.id);
|
||||||
expect(overall).to.have.property('title', state.title);
|
expect(overall).to.have.property('title', state.title);
|
||||||
expect(overall).to.have.property('icon', state.icon);
|
expect(overall).to.have.property('icon', state.icon);
|
||||||
|
@ -65,20 +65,20 @@ describe('ServerStatus class', function () {
|
||||||
|
|
||||||
describe('#toJSON()', function () {
|
describe('#toJSON()', function () {
|
||||||
it('serializes to overall status and individuals', function () {
|
it('serializes to overall status and individuals', function () {
|
||||||
var one = serverStatus.create('one');
|
let one = serverStatus.create('one');
|
||||||
var two = serverStatus.create('two');
|
let two = serverStatus.create('two');
|
||||||
var three = serverStatus.create('three');
|
let three = serverStatus.create('three');
|
||||||
|
|
||||||
one.green();
|
one.green();
|
||||||
two.yellow();
|
two.yellow();
|
||||||
three.red();
|
three.red();
|
||||||
|
|
||||||
var obj = JSON.parse(JSON.stringify(serverStatus));
|
let obj = JSON.parse(JSON.stringify(serverStatus));
|
||||||
expect(obj).to.have.property('overall');
|
expect(obj).to.have.property('overall');
|
||||||
expect(obj.overall.state).to.eql(serverStatus.overall().state);
|
expect(obj.overall.state).to.eql(serverStatus.overall().state);
|
||||||
expect(obj.statuses).to.have.length(3);
|
expect(obj.statuses).to.have.length(3);
|
||||||
|
|
||||||
var outs = _.indexBy(obj.statuses, 'name');
|
let outs = _.indexBy(obj.statuses, 'name');
|
||||||
expect(outs.one).to.have.property('state', 'green');
|
expect(outs.one).to.have.property('state', 'green');
|
||||||
expect(outs.two).to.have.property('state', 'yellow');
|
expect(outs.two).to.have.property('state', 'yellow');
|
||||||
expect(outs.three).to.have.property('state', 'red');
|
expect(outs.three).to.have.property('state', 'red');
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('Status class', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits change when the status is set', function (done) {
|
it('emits change when the status is set', function (done) {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
|
|
||||||
status.once('change', function (prev, prevMsg) {
|
status.once('change', function (prev, prevMsg) {
|
||||||
expect(status.state).to.be('green');
|
expect(status.state).to.be('green');
|
||||||
|
@ -40,8 +40,8 @@ describe('Status class', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only trigger the change listener when something changes', function () {
|
it('should only trigger the change listener when something changes', function () {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
var stub = sinon.stub();
|
let stub = sinon.stub();
|
||||||
status.on('change', stub);
|
status.on('change', stub);
|
||||||
status.green('Ready');
|
status.green('Ready');
|
||||||
status.green('Ready');
|
status.green('Ready');
|
||||||
|
@ -50,17 +50,17 @@ describe('Status class', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a JSON representation of the status', function () {
|
it('should create a JSON representation of the status', function () {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
status.green('Ready');
|
status.green('Ready');
|
||||||
|
|
||||||
var json = status.toJSON();
|
let json = status.toJSON();
|
||||||
expect(json.state).to.eql('green');
|
expect(json.state).to.eql('green');
|
||||||
expect(json.message).to.eql('Ready');
|
expect(json.message).to.eql('Ready');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call on handler if status is already matched', function (done) {
|
it('should call on handler if status is already matched', function (done) {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
var msg = 'Test Ready';
|
let msg = 'Test Ready';
|
||||||
status.green(msg);
|
status.green(msg);
|
||||||
|
|
||||||
status.on('green', function (prev, prevMsg) {
|
status.on('green', function (prev, prevMsg) {
|
||||||
|
@ -73,8 +73,8 @@ describe('Status class', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call once handler if status is already matched', function (done) {
|
it('should call once handler if status is already matched', function (done) {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
var msg = 'Test Ready';
|
let msg = 'Test Ready';
|
||||||
status.green(msg);
|
status.green(msg);
|
||||||
|
|
||||||
status.once('green', function (prev, prevMsg) {
|
status.once('green', function (prev, prevMsg) {
|
||||||
|
@ -88,16 +88,16 @@ describe('Status class', function () {
|
||||||
|
|
||||||
function testState(color) {
|
function testState(color) {
|
||||||
it(`should change the state to ${color} when #${color}() is called`, function () {
|
it(`should change the state to ${color} when #${color}() is called`, function () {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
var message = 'testing ' + color;
|
let message = 'testing ' + color;
|
||||||
status[color](message);
|
status[color](message);
|
||||||
expect(status).to.have.property('state', color);
|
expect(status).to.have.property('state', color);
|
||||||
expect(status).to.have.property('message', message);
|
expect(status).to.have.property('message', message);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should trigger the "change" listner when #${color}() is called`, function (done) {
|
it(`should trigger the "change" listner when #${color}() is called`, function (done) {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
var message = 'testing ' + color;
|
let message = 'testing ' + color;
|
||||||
status.on('change', function (prev, prevMsg) {
|
status.on('change', function (prev, prevMsg) {
|
||||||
expect(status.state).to.be(color);
|
expect(status.state).to.be(color);
|
||||||
expect(status.message).to.be(message);
|
expect(status.message).to.be(message);
|
||||||
|
@ -110,8 +110,8 @@ describe('Status class', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should trigger the "${color}" listner when #${color}() is called`, function (done) {
|
it(`should trigger the "${color}" listner when #${color}() is called`, function (done) {
|
||||||
var status = serverStatus.create('test');
|
let status = serverStatus.create('test');
|
||||||
var message = 'testing ' + color;
|
let message = 'testing ' + color;
|
||||||
status.on(color, function (prev, prevMsg) {
|
status.on(color, function (prev, prevMsg) {
|
||||||
expect(status.state).to.be(color);
|
expect(status.state).to.be(color);
|
||||||
expect(status.message).to.be(message);
|
expect(status.message).to.be(message);
|
||||||
|
|
|
@ -22,8 +22,8 @@ module.exports = function (kbnServer, server, config) {
|
||||||
});
|
});
|
||||||
|
|
||||||
server.decorate('reply', 'renderStatusPage', function () {
|
server.decorate('reply', 'renderStatusPage', function () {
|
||||||
var app = kbnServer.uiExports.getHiddenApp('status_page');
|
let app = kbnServer.uiExports.getHiddenApp('status_page');
|
||||||
var resp = app ? this.renderApp(app) : this(kbnServer.status.toString());
|
let resp = app ? this.renderApp(app) : this(kbnServer.status.toString());
|
||||||
resp.code(kbnServer.status.isGreen() ? 200 : 503);
|
resp.code(kbnServer.status.isGreen() ? 200 : 503);
|
||||||
return resp;
|
return resp;
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ module.exports = function (kbnServer, server, config) {
|
||||||
let secSinceLast = (now - lastReport) / 1000;
|
let secSinceLast = (now - lastReport) / 1000;
|
||||||
lastReport = now;
|
lastReport = now;
|
||||||
|
|
||||||
var port = config.get('server.port');
|
let port = config.get('server.port');
|
||||||
let requests = _.get(event, ['requests', port, 'total'], 0);
|
let requests = _.get(event, ['requests', port, 'total'], 0);
|
||||||
let requestsPerSecond = requests / secSinceLast;
|
let requestsPerSecond = requests / secSinceLast;
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ function Samples(max) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Samples.prototype.add = function (sample) {
|
Samples.prototype.add = function (sample) {
|
||||||
var vals = this.vals;
|
let vals = this.vals;
|
||||||
var length = this.length = Math.min(this.length + 1, this.max);
|
let length = this.length = Math.min(this.length + 1, this.max);
|
||||||
|
|
||||||
_.forOwn(sample, function (val, name) {
|
_.forOwn(sample, function (val, name) {
|
||||||
if (val == null) val = null;
|
if (val == null) val = null;
|
||||||
|
|
|
@ -31,15 +31,15 @@ module.exports = class ServerStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
overall() {
|
overall() {
|
||||||
var state = _(this._created)
|
let state = _(this._created)
|
||||||
.map(function (status) {
|
.map(function (status) {
|
||||||
return states.get(status.state);
|
return states.get(status.state);
|
||||||
})
|
})
|
||||||
.sortBy('severity')
|
.sortBy('severity')
|
||||||
.pop();
|
.pop();
|
||||||
|
|
||||||
var statuses = _.where(this._created, { state: state.id });
|
let statuses = _.where(this._created, { state: state.id });
|
||||||
var since = _.get(_.sortBy(statuses, 'since'), [0, 'since']);
|
let since = _.get(_.sortBy(statuses, 'since'), [0, 'since']);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: state.id,
|
state: state.id,
|
||||||
|
@ -59,7 +59,7 @@ module.exports = class ServerStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
var overall = this.overall();
|
let overall = this.overall();
|
||||||
return `${overall.title} – ${overall.nickname}`;
|
return `${overall.title} – ${overall.nickname}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class Status extends EventEmitter {
|
||||||
|
|
||||||
this.on('change', function (previous, previousMsg) {
|
this.on('change', function (previous, previousMsg) {
|
||||||
this.since = new Date();
|
this.since = new Date();
|
||||||
var tags = ['status', name];
|
let tags = ['status', name];
|
||||||
tags.push(this.state === 'red' ? 'error' : 'info');
|
tags.push(this.state === 'red' ? 'error' : 'info');
|
||||||
|
|
||||||
server.log(tags, {
|
server.log(tags, {
|
||||||
|
|
|
@ -8,7 +8,7 @@ Bluebird.longStackTraces();
|
||||||
* replace the Promise service with Bluebird so that tests
|
* replace the Promise service with Bluebird so that tests
|
||||||
* can use promises without having to call $rootScope.apply()
|
* can use promises without having to call $rootScope.apply()
|
||||||
*
|
*
|
||||||
* var noDigestPromises = require('test_utils/no_digest_promises');
|
* let noDigestPromises = require('test_utils/no_digest_promises');
|
||||||
*
|
*
|
||||||
* describe('some module that does complex shit with promises', function () {
|
* describe('some module that does complex shit with promises', function () {
|
||||||
* beforeEach(noDigestPromises.activate);
|
* beforeEach(noDigestPromises.activate);
|
||||||
|
@ -16,7 +16,7 @@ Bluebird.longStackTraces();
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var active = false;
|
let active = false;
|
||||||
|
|
||||||
uiModules
|
uiModules
|
||||||
.get('kibana')
|
.get('kibana')
|
||||||
|
|
|
@ -2,8 +2,8 @@ import $ from 'jquery';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import keyMap from 'ui/utils/key_map';
|
import keyMap from 'ui/utils/key_map';
|
||||||
var reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1));
|
let reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1));
|
||||||
var KeyboardEvent = window.KeyboardEvent;
|
let KeyboardEvent = window.KeyboardEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate keyboard events in an element. This allows testing the way that
|
* Simulate keyboard events in an element. This allows testing the way that
|
||||||
|
@ -35,7 +35,7 @@ var KeyboardEvent = window.KeyboardEvent;
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export default function ($el, sequence) {
|
export default function ($el, sequence) {
|
||||||
var modifierState = {
|
let modifierState = {
|
||||||
ctrlKey: false,
|
ctrlKey: false,
|
||||||
shiftKey: false,
|
shiftKey: false,
|
||||||
altKey: false,
|
altKey: false,
|
||||||
|
@ -45,7 +45,7 @@ export default function ($el, sequence) {
|
||||||
return doList(_.clone(sequence));
|
return doList(_.clone(sequence));
|
||||||
|
|
||||||
function setModifier(key, state) {
|
function setModifier(key, state) {
|
||||||
var name = key + 'Key';
|
let name = key + 'Key';
|
||||||
if (modifierState.hasOwnProperty(name)) {
|
if (modifierState.hasOwnProperty(name)) {
|
||||||
modifierState[name] = !!state;
|
modifierState[name] = !!state;
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ export default function ($el, sequence) {
|
||||||
return Promise.try(function () {
|
return Promise.try(function () {
|
||||||
if (!list || !list.length) return;
|
if (!list || !list.length) return;
|
||||||
|
|
||||||
var event = list[0];
|
let event = list[0];
|
||||||
if (_.isString(event)) {
|
if (_.isString(event)) {
|
||||||
event = { type: 'press', key: event };
|
event = { type: 'press', key: event };
|
||||||
}
|
}
|
||||||
|
@ -91,14 +91,14 @@ export default function ($el, sequence) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fire(type, key, repeat) {
|
function fire(type, key, repeat) {
|
||||||
var keyCode = reverseKeyMap[key];
|
let keyCode = reverseKeyMap[key];
|
||||||
if (!keyCode) throw new TypeError('invalid key "' + key + '"');
|
if (!keyCode) throw new TypeError('invalid key "' + key + '"');
|
||||||
|
|
||||||
if (type === 'keydown') setModifier(key, true);
|
if (type === 'keydown') setModifier(key, true);
|
||||||
if (type === 'keyup') setModifier(key, false);
|
if (type === 'keyup') setModifier(key, false);
|
||||||
|
|
||||||
var $target = _.isFunction($el) ? $el() : $el;
|
let $target = _.isFunction($el) ? $el() : $el;
|
||||||
var $event = new $.Event(type, _.defaults({ keyCode: keyCode }, modifierState));
|
let $event = new $.Event(type, _.defaults({ keyCode: keyCode }, modifierState));
|
||||||
$target.trigger($event);
|
$target.trigger($event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,10 +9,10 @@ import RegistryFieldFormatsProvider from 'ui/registry/field_formats';
|
||||||
import IndexPatternsFlattenHitProvider from 'ui/index_patterns/_flatten_hit';
|
import IndexPatternsFlattenHitProvider from 'ui/index_patterns/_flatten_hit';
|
||||||
import IndexPatternsFieldProvider from 'ui/index_patterns/_field';
|
import IndexPatternsFieldProvider from 'ui/index_patterns/_field';
|
||||||
export default function (Private) {
|
export default function (Private) {
|
||||||
var fieldFormats = Private(RegistryFieldFormatsProvider);
|
let fieldFormats = Private(RegistryFieldFormatsProvider);
|
||||||
var flattenHit = Private(IndexPatternsFlattenHitProvider);
|
let flattenHit = Private(IndexPatternsFlattenHitProvider);
|
||||||
|
|
||||||
var Field = Private(IndexPatternsFieldProvider);
|
let Field = Private(IndexPatternsFieldProvider);
|
||||||
|
|
||||||
function StubIndexPattern(pattern, timeField, fields) {
|
function StubIndexPattern(pattern, timeField, fields) {
|
||||||
this.id = pattern;
|
this.id = pattern;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import 'angular';
|
import 'angular';
|
||||||
import 'ui/chrome';
|
import 'ui/chrome';
|
||||||
import 'ui/chrome/context';
|
|
||||||
import 'ui/bind';
|
import 'ui/bind';
|
||||||
import 'ui/bound_to_config_obj';
|
import 'ui/bound_to_config_obj';
|
||||||
import 'ui/config';
|
import 'ui/config';
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
const store = Symbol('store');
|
const store = Symbol('store');
|
||||||
|
|
||||||
export default class TabFakeStore {
|
export default class StubBrowserStorage {
|
||||||
constructor() { this[store] = new Map(); }
|
constructor() { this[store] = new Map(); }
|
||||||
getItem(k) { return this[store].get(k); }
|
getItem(k) { return this[store].get(k); }
|
||||||
setItem(k, v) { return this[store].set(k, v); }
|
setItem(k, v) { return this[store].set(k, String(v)); }
|
||||||
removeItem(k) { return this[store].delete(k); }
|
removeItem(k) { return this[store].delete(k); }
|
||||||
getKeys() { return [ ...this[store].keys() ]; }
|
getKeys() { return [ ...this[store].keys() ]; }
|
||||||
getValues() { return [ ...this[store].values() ]; }
|
getValues() { return [ ...this[store].values() ]; }
|
36
src/ui/public/chrome/__tests__/kbn_loading_indicator.js
Normal file
36
src/ui/public/chrome/__tests__/kbn_loading_indicator.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import ngMock from 'ng_mock';
|
||||||
|
import expect from 'expect.js';
|
||||||
|
import uiModules from 'ui/modules';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import '../directives/kbn_loading_indicator';
|
||||||
|
|
||||||
|
|
||||||
|
describe('kbnLoadingIndicator', function () {
|
||||||
|
let compile;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
ngMock.module('kibana');
|
||||||
|
ngMock.inject(function ($compile, $rootScope) {
|
||||||
|
compile = function (hasActiveConnections) {
|
||||||
|
$rootScope.chrome = {
|
||||||
|
httpActive: (hasActiveConnections ? [1] : [])
|
||||||
|
};
|
||||||
|
const $el = $('<kbn-loading-indicator></kbn-loading-indicator>');
|
||||||
|
$rootScope.$apply();
|
||||||
|
$compile($el)($rootScope);
|
||||||
|
return $el;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('injects a loading .spinner into the element', function () {
|
||||||
|
const $el = compile();
|
||||||
|
expect($el.find('.spinner')).to.have.length(1);
|
||||||
|
});
|
||||||
|
it('applies removes ng-hide class when there are connections', function () {
|
||||||
|
const $el = compile(true);
|
||||||
|
expect($el.find('.spinner.ng-hide')).to.have.length(0);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,7 +1,8 @@
|
||||||
import sinon from 'auto-release-sinon';
|
import sinon from 'auto-release-sinon';
|
||||||
|
|
||||||
import Tab from '../tab';
|
import Tab from '../tab';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import TabFakeStore from './_tab_fake_store';
|
import StubBrowserStorage from './fixtures/stub_browser_storage';
|
||||||
|
|
||||||
describe('Chrome Tab', function () {
|
describe('Chrome Tab', function () {
|
||||||
describe('construction', function () {
|
describe('construction', function () {
|
||||||
|
@ -88,7 +89,7 @@ describe('Chrome Tab', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('discovers the lastUrl', function () {
|
it('discovers the lastUrl', function () {
|
||||||
const lastUrlStore = new TabFakeStore();
|
const lastUrlStore = new StubBrowserStorage();
|
||||||
const tab = new Tab({ id: 'foo', lastUrlStore });
|
const tab = new Tab({ id: 'foo', lastUrlStore });
|
||||||
expect(tab.lastUrl).to.not.equal('/foo/bar');
|
expect(tab.lastUrl).to.not.equal('/foo/bar');
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ describe('Chrome Tab', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs a warning about last urls that do not match the rootUrl', function () {
|
it('logs a warning about last urls that do not match the rootUrl', function () {
|
||||||
const lastUrlStore = new TabFakeStore();
|
const lastUrlStore = new StubBrowserStorage();
|
||||||
const tab = new Tab({ id: 'foo', baseUrl: '/bar', lastUrlStore });
|
const tab = new Tab({ id: 'foo', baseUrl: '/bar', lastUrlStore });
|
||||||
tab.setLastUrl('/bar/foo/1');
|
tab.setLastUrl('/bar/foo/1');
|
||||||
|
|
||||||
|
@ -114,7 +115,7 @@ describe('Chrome Tab', function () {
|
||||||
|
|
||||||
describe('#setLastUrl()', function () {
|
describe('#setLastUrl()', function () {
|
||||||
it('updates the lastUrl and storage value if passed a lastUrlStore', function () {
|
it('updates the lastUrl and storage value if passed a lastUrlStore', function () {
|
||||||
const lastUrlStore = new TabFakeStore();
|
const lastUrlStore = new StubBrowserStorage();
|
||||||
const tab = new Tab({ id: 'foo', lastUrlStore });
|
const tab = new Tab({ id: 'foo', lastUrlStore });
|
||||||
|
|
||||||
expect(tab.lastUrl).to.not.equal('foo');
|
expect(tab.lastUrl).to.not.equal('foo');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
|
|
||||||
import TabFakeStore from './_tab_fake_store';
|
import StubBrowserStorage from './fixtures/stub_browser_storage';
|
||||||
import TabCollection from '../tab_collection';
|
import TabCollection from '../tab_collection';
|
||||||
import Tab from '../tab';
|
import Tab from '../tab';
|
||||||
import { indexBy, random } from 'lodash';
|
import { indexBy, random } from 'lodash';
|
||||||
|
@ -54,7 +54,7 @@ describe('Chrome TabCollection', function () {
|
||||||
|
|
||||||
describe('#consumeRouteUpdate()', function () {
|
describe('#consumeRouteUpdate()', function () {
|
||||||
it('updates the active tab', function () {
|
it('updates the active tab', function () {
|
||||||
const store = new TabFakeStore();
|
const store = new StubBrowserStorage();
|
||||||
const baseUrl = `http://localhost:${random(1000, 9999)}`;
|
const baseUrl = `http://localhost:${random(1000, 9999)}`;
|
||||||
const tabs = new TabCollection({ store, defaults: { baseUrl } });
|
const tabs = new TabCollection({ store, defaults: { baseUrl } });
|
||||||
tabs.set([
|
tabs.set([
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
|
|
||||||
import kbnAngular from '../angular';
|
import kbnAngular from '../angular';
|
||||||
import TabFakeStore from '../../__tests__/_tab_fake_store';
|
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
describe('Chrome API :: Angular', () => {
|
describe('Chrome API :: Angular', () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
|
|
||||||
import setup from '../apps';
|
import setup from '../apps';
|
||||||
import TabFakeStore from '../../__tests__/_tab_fake_store';
|
import StubBrowserStorage from '../../__tests__/fixtures/stub_browser_storage';
|
||||||
|
|
||||||
describe('Chrome API :: apps', function () {
|
describe('Chrome API :: apps', function () {
|
||||||
describe('#get/setShowAppsLink()', function () {
|
describe('#get/setShowAppsLink()', function () {
|
||||||
|
@ -147,7 +147,7 @@ describe('Chrome API :: apps', function () {
|
||||||
describe('#get/setLastUrlFor()', function () {
|
describe('#get/setLastUrlFor()', function () {
|
||||||
it('reads/writes last url from storage', function () {
|
it('reads/writes last url from storage', function () {
|
||||||
const chrome = {};
|
const chrome = {};
|
||||||
const store = new TabFakeStore();
|
const store = new StubBrowserStorage();
|
||||||
setup(chrome, { appUrlStore: store });
|
setup(chrome, { appUrlStore: store });
|
||||||
expect(chrome.getLastUrlFor('app')).to.equal(undefined);
|
expect(chrome.getLastUrlFor('app')).to.equal(undefined);
|
||||||
chrome.setLastUrlFor('app', 'url');
|
chrome.setLastUrlFor('app', 'url');
|
||||||
|
|
|
@ -1,45 +1,73 @@
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
|
|
||||||
import initChromeNavApi from 'ui/chrome/api/nav';
|
import initChromeNavApi from 'ui/chrome/api/nav';
|
||||||
|
import StubBrowserStorage from '../../__tests__/fixtures/stub_browser_storage';
|
||||||
|
|
||||||
const basePath = '/someBasePath';
|
const basePath = '/someBasePath';
|
||||||
|
|
||||||
function getChrome(customInternals = { basePath }) {
|
function init(customInternals = { basePath }) {
|
||||||
const chrome = {};
|
const chrome = {};
|
||||||
initChromeNavApi(chrome, {
|
const internals = {
|
||||||
nav: [],
|
nav: [],
|
||||||
...customInternals,
|
...customInternals,
|
||||||
});
|
};
|
||||||
return chrome;
|
initChromeNavApi(chrome, internals);
|
||||||
|
return { chrome, internals };
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('chrome nav apis', function () {
|
describe('chrome nav apis', function () {
|
||||||
describe('#getBasePath()', function () {
|
describe('#getBasePath()', function () {
|
||||||
it('returns the basePath', function () {
|
it('returns the basePath', function () {
|
||||||
const chrome = getChrome();
|
const { chrome } = init();
|
||||||
expect(chrome.getBasePath()).to.be(basePath);
|
expect(chrome.getBasePath()).to.be(basePath);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#addBasePath()', function () {
|
describe('#addBasePath()', function () {
|
||||||
it('returns undefined when nothing is passed', function () {
|
it('returns undefined when nothing is passed', function () {
|
||||||
const chrome = getChrome();
|
const { chrome } = init();
|
||||||
expect(chrome.addBasePath()).to.be(undefined);
|
expect(chrome.addBasePath()).to.be(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prepends the base path when the input is a path', function () {
|
it('prepends the base path when the input is a path', function () {
|
||||||
const chrome = getChrome();
|
const { chrome } = init();
|
||||||
expect(chrome.addBasePath('/other/path')).to.be(`${basePath}/other/path`);
|
expect(chrome.addBasePath('/other/path')).to.be(`${basePath}/other/path`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores non-path urls', function () {
|
it('ignores non-path urls', function () {
|
||||||
const chrome = getChrome();
|
const { chrome } = init();
|
||||||
expect(chrome.addBasePath('http://github.com/elastic/kibana')).to.be('http://github.com/elastic/kibana');
|
expect(chrome.addBasePath('http://github.com/elastic/kibana')).to.be('http://github.com/elastic/kibana');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('includes the query string', function () {
|
it('includes the query string', function () {
|
||||||
const chrome = getChrome();
|
const { chrome } = init();
|
||||||
expect(chrome.addBasePath('/app/kibana?a=b')).to.be(`${basePath}/app/kibana?a=b`);
|
expect(chrome.addBasePath('/app/kibana?a=b')).to.be(`${basePath}/app/kibana?a=b`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('internals.trackPossibleSubUrl()', function () {
|
||||||
|
it('injects the globalState of the current url to all links for the same app', function () {
|
||||||
|
const appUrlStore = new StubBrowserStorage();
|
||||||
|
const nav = [
|
||||||
|
{ url: 'https://localhost:9200/app/kibana#discover' },
|
||||||
|
{ url: 'https://localhost:9200/app/kibana#visualize' },
|
||||||
|
{ url: 'https://localhost:9200/app/kibana#dashboard' },
|
||||||
|
].map(l => {
|
||||||
|
l.lastSubUrl = l.url;
|
||||||
|
return l;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { chrome, internals } = init({ appUrlStore, nav });
|
||||||
|
|
||||||
|
internals.trackPossibleSubUrl('https://localhost:9200/app/kibana#dashboard?_g=globalstate');
|
||||||
|
expect(internals.nav[0].lastSubUrl).to.be('https://localhost:9200/app/kibana#discover?_g=globalstate');
|
||||||
|
expect(internals.nav[0].active).to.be(false);
|
||||||
|
|
||||||
|
expect(internals.nav[1].lastSubUrl).to.be('https://localhost:9200/app/kibana#visualize?_g=globalstate');
|
||||||
|
expect(internals.nav[1].active).to.be(false);
|
||||||
|
|
||||||
|
expect(internals.nav[2].lastSubUrl).to.be('https://localhost:9200/app/kibana#dashboard?_g=globalstate');
|
||||||
|
expect(internals.nav[2].active).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,25 +40,72 @@ export default function (chrome, internals) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshLastUrl(link) {
|
function refreshLastUrl(link) {
|
||||||
link.lastSubUrl = internals.appUrlStore.getItem(lastSubUrlKey(link));
|
link.lastSubUrl = internals.appUrlStore.getItem(lastSubUrlKey(link)) || link.lastSubUrl || link.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppId(url) {
|
||||||
|
const pathname = parse(url).pathname;
|
||||||
|
const pathnameWithoutBasepath = pathname.slice(chrome.getBasePath().length);
|
||||||
|
const match = pathnameWithoutBasepath.match(/^\/app\/([^\/]+)(?:\/|\?|#|$)/);
|
||||||
|
if (match) return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeKibanaUrl(url) {
|
||||||
|
const parsedUrl = parse(url, true);
|
||||||
|
const appId = getAppId(parsedUrl);
|
||||||
|
const hash = parsedUrl.hash || '';
|
||||||
|
const parsedHash = parse(hash.slice(1), true);
|
||||||
|
const globalState = parsedHash.query && parsedHash.query._g;
|
||||||
|
return { appId, globalState, parsedUrl, parsedHash };
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectNewGlobalState(link, fromAppId, newGlobalState) {
|
||||||
|
// parse the lastSubUrl of this link so we can manipulate its parts
|
||||||
|
const { appId: toAppId, parsedHash: toHash, parsedUrl: toParsed } = decodeKibanaUrl(link.lastSubUrl);
|
||||||
|
|
||||||
|
// don't copy global state if links are for different apps
|
||||||
|
if (fromAppId !== toAppId) return;
|
||||||
|
|
||||||
|
// add the new globalState to the hashUrl in the linkurl
|
||||||
|
const toHashQuery = toHash.query || {};
|
||||||
|
toHashQuery._g = newGlobalState;
|
||||||
|
|
||||||
|
// format the new subUrl and include the newHash
|
||||||
|
link.lastSubUrl = format({
|
||||||
|
protocol: toParsed.protocol,
|
||||||
|
port: toParsed.port,
|
||||||
|
hostname: toParsed.hostname,
|
||||||
|
pathname: toParsed.pathname,
|
||||||
|
query: toParsed.query,
|
||||||
|
hash: format({
|
||||||
|
pathname: toHash.pathname,
|
||||||
|
query: toHashQuery,
|
||||||
|
hash: toHash.hash,
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
internals.trackPossibleSubUrl = function (url) {
|
internals.trackPossibleSubUrl = function (url) {
|
||||||
for (const link of internals.nav) {
|
const { appId, globalState: newGlobalState } = decodeKibanaUrl(url);
|
||||||
link.active = startsWith(url, link.url);
|
|
||||||
|
|
||||||
|
for (const link of internals.nav) {
|
||||||
|
const matchingTab = find(internals.tabs, { rootUrl: link.url });
|
||||||
|
|
||||||
|
link.active = startsWith(url, link.url);
|
||||||
if (link.active) {
|
if (link.active) {
|
||||||
setLastUrl(link, url);
|
setLastUrl(link, url);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matchingTab = find(internals.tabs, { rootUrl: link.url });
|
|
||||||
if (matchingTab) {
|
if (matchingTab) {
|
||||||
setLastUrl(link, matchingTab.getLastUrl());
|
setLastUrl(link, matchingTab.getLastUrl());
|
||||||
continue;
|
} else {
|
||||||
|
refreshLastUrl(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshLastUrl(link);
|
if (newGlobalState) {
|
||||||
|
injectNewGlobalState(link, appId, newGlobalState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="content" chrome-context >
|
<div class="content" chrome-context >
|
||||||
<!-- TODO: These config dropdowns shouldn't be hard coded -->
|
<!-- TODO: These config dropdowns shouldn't be hard coded -->
|
||||||
<nav class="app-links-wrapper">
|
<nav class="app-links-wrapper" ng-show="chrome.getVisible()">
|
||||||
<li
|
<li
|
||||||
ng-if="!chrome.getBrand('logo') && !chrome.getBrand('smallLogo')"
|
ng-if="!chrome.getBrand('logo') && !chrome.getBrand('smallLogo')"
|
||||||
aria-label="{{ chrome.getAppTitle() }} Logo"
|
aria-label="{{ chrome.getAppTitle() }} Logo"
|
||||||
|
@ -21,28 +21,12 @@
|
||||||
|
|
||||||
<app-switcher>
|
<app-switcher>
|
||||||
</app-switcher>
|
</app-switcher>
|
||||||
<div class="bottom-apps hide app-links">
|
<div class="bottom-apps">
|
||||||
<div class="app-link">
|
<div class="chrome-actions app-links" kbn-chrome-append-nav-controls></div>
|
||||||
<a href="http://elastic.co">
|
|
||||||
<div class="app-icon">
|
|
||||||
<i class="fa fa-gear"></i>
|
|
||||||
</div>
|
|
||||||
<div class="app-title">settings</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="app-link">
|
|
||||||
<a href="http://elastic.co">
|
|
||||||
<div class="app-icon">
|
|
||||||
<i class="fa fa-user"></i>
|
|
||||||
</div>
|
|
||||||
<div class="app-title">Jon Doe</div>
|
|
||||||
<div class="app-title">Logout</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="app-wrapper">
|
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
|
||||||
<div class="app-wrapper-panel">
|
<div class="app-wrapper-panel">
|
||||||
<kbn-notifications list="notifList"></kbn-notifications>
|
<kbn-notifications list="notifList"></kbn-notifications>
|
||||||
<nav
|
<nav
|
||||||
|
@ -80,6 +64,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- /Full navbar -->
|
<!-- /Full navbar -->
|
||||||
</nav>
|
</nav>
|
||||||
|
<kbn-loading-indicator></kbn-loading-indicator>
|
||||||
<div class="application" ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()" ng-view></div>
|
<div class="application" ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()" ng-view></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<kbn-timepicker
|
<kbn-timepicker
|
||||||
from="opts.timefilter.time.from"
|
from="timefilter.time.from"
|
||||||
to="opts.timefilter.time.to"
|
to="timefilter.time.to"
|
||||||
mode="opts.timefilter.time.mode"
|
mode="timefilter.time.mode"
|
||||||
active-tab="'filter'"
|
active-tab="'filter'"
|
||||||
interval="opts.timefilter.refreshInterval">
|
interval="timefilter.refreshInterval">
|
||||||
</kbn-timepicker>
|
</kbn-timepicker>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<kbn-timepicker
|
<kbn-timepicker
|
||||||
from="opts.timefilter.time.from"
|
from="timefilter.time.from"
|
||||||
to="opts.timefilter.time.to"
|
to="timefilter.time.to"
|
||||||
mode="opts.timefilter.time.mode"
|
mode="timefilter.time.mode"
|
||||||
active-tab="'interval'"
|
active-tab="'interval'"
|
||||||
interval="opts.timefilter.refreshInterval">
|
interval="timefilter.refreshInterval">
|
||||||
</kbn-timepicker>
|
</kbn-timepicker>
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import ConfigTemplate from 'ui/config_template';
|
|
||||||
import uiModules from 'ui/modules';
|
|
||||||
|
|
||||||
uiModules
|
|
||||||
.get('kibana')
|
|
||||||
// TODO: all of this really belongs in the timepicker
|
|
||||||
.directive('chromeContext', function (timefilter, globalState) {
|
|
||||||
|
|
||||||
var listenForUpdates = _.once(function ($scope) {
|
|
||||||
$scope.$listen(timefilter, 'update', function (newVal, oldVal) {
|
|
||||||
globalState.time = _.clone(timefilter.time);
|
|
||||||
globalState.refreshInterval = _.clone(timefilter.refreshInterval);
|
|
||||||
globalState.save();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
link: function ($scope) {
|
|
||||||
listenForUpdates($scope);
|
|
||||||
|
|
||||||
// chrome is responsible for timepicker ui and state transfer...
|
|
||||||
$scope.timefilter = timefilter;
|
|
||||||
$scope.toggleRefresh = function () {
|
|
||||||
timefilter.refreshInterval.pause = !timefilter.refreshInterval.pause;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<div class="spinner" ng-show="chrome.httpActive.length"></div>
|
|
|
@ -54,6 +54,7 @@ body { overflow-x: hidden; }
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
|
&.hidden-chrome { left: 0; }
|
||||||
&-panel {
|
&-panel {
|
||||||
.flex-parent(@shrink: 0);
|
.flex-parent(@shrink: 0);
|
||||||
box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
|
box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
|
||||||
|
@ -84,7 +85,6 @@ body { overflow-x: hidden; }
|
||||||
|
|
||||||
.app-icon {
|
.app-icon {
|
||||||
float: left;
|
float: left;
|
||||||
filter: invert(100%);
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
|
@ -95,6 +95,11 @@ body { overflow-x: hidden; }
|
||||||
> img {
|
> img {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
> i {
|
||||||
|
color: #fff;
|
||||||
|
line-height: @app-icon-height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +133,7 @@ body { overflow-x: hidden; }
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
filter: invert(100%);
|
filter: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,6 @@ import $ from 'jquery';
|
||||||
|
|
||||||
import chromeNavControlsRegistry from 'ui/registry/chrome_nav_controls';
|
import chromeNavControlsRegistry from 'ui/registry/chrome_nav_controls';
|
||||||
import UiModules from 'ui/modules';
|
import UiModules from 'ui/modules';
|
||||||
import spinnerHtml from './active_http_spinner.html';
|
|
||||||
|
|
||||||
const spinner = {
|
|
||||||
name: 'active http requests',
|
|
||||||
template: spinnerHtml
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function (chrome, internals) {
|
export default function (chrome, internals) {
|
||||||
|
|
||||||
|
@ -19,7 +13,7 @@ export default function (chrome, internals) {
|
||||||
const parts = [$element.html()];
|
const parts = [$element.html()];
|
||||||
const controls = Private(chromeNavControlsRegistry);
|
const controls = Private(chromeNavControlsRegistry);
|
||||||
|
|
||||||
for (const control of [spinner, ...controls.inOrder]) {
|
for (const control of controls.inOrder) {
|
||||||
parts.unshift(
|
parts.unshift(
|
||||||
`<!-- nav control ${control.name} -->`,
|
`<!-- nav control ${control.name} -->`,
|
||||||
control.template
|
control.template
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import 'ui/directives/config';
|
import 'ui/directives/kbn_top_nav';
|
||||||
|
|
||||||
import './app_switcher';
|
import './app_switcher';
|
||||||
import kbnChromeProv from './kbn_chrome';
|
import kbnChromeProv from './kbn_chrome';
|
||||||
import kbnChromeNavControlsProv from './append_nav_controls';
|
import kbnChromeNavControlsProv from './append_nav_controls';
|
||||||
|
import './kbn_loading_indicator';
|
||||||
|
|
||||||
export default function (chrome, internals) {
|
export default function (chrome, internals) {
|
||||||
kbnChromeProv(chrome, internals);
|
kbnChromeProv(chrome, internals);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
import UiModules from 'ui/modules';
|
import UiModules from 'ui/modules';
|
||||||
import ConfigTemplate from 'ui/config_template';
|
|
||||||
|
|
||||||
export default function (chrome, internals) {
|
export default function (chrome, internals) {
|
||||||
|
|
||||||
|
@ -46,10 +45,6 @@ export default function (chrome, internals) {
|
||||||
// and some local values
|
// and some local values
|
||||||
chrome.httpActive = $http.pendingRequests;
|
chrome.httpActive = $http.pendingRequests;
|
||||||
$scope.notifList = require('ui/notify')._notifs;
|
$scope.notifList = require('ui/notify')._notifs;
|
||||||
$scope.appSwitcherTemplate = new ConfigTemplate({
|
|
||||||
switcher: '<app-switcher></app-switcher>'
|
|
||||||
});
|
|
||||||
|
|
||||||
return chrome;
|
return chrome;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
13
src/ui/public/chrome/directives/kbn_loading_indicator.js
Normal file
13
src/ui/public/chrome/directives/kbn_loading_indicator.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import UiModules from 'ui/modules';
|
||||||
|
import angular from 'angular';
|
||||||
|
|
||||||
|
const spinnerTemplate = '<div class="spinner" ng-show="chrome.httpActive.length"></div>';
|
||||||
|
|
||||||
|
UiModules
|
||||||
|
.get('ui/kibana')
|
||||||
|
.directive('kbnLoadingIndicator', function ($compile) {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
template: spinnerTemplate,
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,34 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
function ConfigTemplate(templates) {
|
|
||||||
var template = this;
|
|
||||||
template.current = null;
|
|
||||||
template.toggle = _.partial(update, null);
|
|
||||||
template.open = _.partial(update, true);
|
|
||||||
template.close = _.partial(update, false);
|
|
||||||
|
|
||||||
function update(newState, name) {
|
|
||||||
var toUpdate = templates[name];
|
|
||||||
var curState = template.is(name);
|
|
||||||
if (newState == null) newState = !curState;
|
|
||||||
|
|
||||||
if (newState) {
|
|
||||||
template.current = toUpdate;
|
|
||||||
} else {
|
|
||||||
template.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
template.is = function (name) {
|
|
||||||
return template.current === templates[name];
|
|
||||||
};
|
|
||||||
|
|
||||||
template.toString = function () {
|
|
||||||
return template.current;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ConfigTemplate;
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ngMock from 'ng_mock';
|
||||||
import expect from 'expect.js';
|
import expect from 'expect.js';
|
||||||
import NormalizeSortRequestProvider from 'ui/courier/data_source/_normalize_sort_request';
|
import NormalizeSortRequestProvider from 'ui/courier/data_source/_normalize_sort_request';
|
||||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
describe('SearchSource#normalizeSortRequest', function () {
|
describe('SearchSource#normalizeSortRequest', function () {
|
||||||
let normalizeSortRequest;
|
let normalizeSortRequest;
|
||||||
|
@ -87,4 +88,17 @@ describe('SearchSource#normalizeSortRequest', function () {
|
||||||
|
|
||||||
expect(result).to.eql([normalizedSort]);
|
expect(result).to.eql([normalizedSort]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should remove unmapped_type parameter from _score sorting', function () {
|
||||||
|
var sortable = { _score: 'desc'};
|
||||||
|
var expected = [{
|
||||||
|
_score: {
|
||||||
|
order: 'desc'
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
var result = normalizeSortRequest(sortable, indexPattern);
|
||||||
|
expect(_.isEqual(result, expected)).to.be.ok();
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,6 +43,10 @@ export default function normalizeSortRequest(config) {
|
||||||
sortValue = { order: sortValue };
|
sortValue = { order: sortValue };
|
||||||
}
|
}
|
||||||
sortValue = _.defaults({}, sortValue, defaultSortOptions);
|
sortValue = _.defaults({}, sortValue, defaultSortOptions);
|
||||||
|
|
||||||
|
if (sortField === '_score') {
|
||||||
|
delete sortValue.unmapped_type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized[sortField] = sortValue;
|
normalized[sortField] = sortValue;
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
import ngMock from 'ng_mock';
|
|
||||||
import expect from 'expect.js';
|
|
||||||
import { assign } from 'lodash';
|
|
||||||
import $ from 'jquery';
|
|
||||||
|
|
||||||
describe('Config Directive', function () {
|
|
||||||
|
|
||||||
var build = function () {};
|
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana', function ($compileProvider) {
|
|
||||||
var renderCount = 0;
|
|
||||||
$compileProvider.directive('renderCounter', function () {
|
|
||||||
return {
|
|
||||||
link: function ($scope, $el) {
|
|
||||||
$el.html(++renderCount);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(ngMock.inject(function ($compile, $rootScope) {
|
|
||||||
|
|
||||||
build = function (attrs, scopeVars) {
|
|
||||||
var $el = $('<config>').attr(attrs);
|
|
||||||
var $scope = $rootScope.$new();
|
|
||||||
assign($scope, scopeVars || {});
|
|
||||||
$compile($el)($scope);
|
|
||||||
$scope.$digest();
|
|
||||||
return $el;
|
|
||||||
};
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('renders it\'s config template', function () {
|
|
||||||
var $config = build({ 'config-template': '"<uniqel></uniqel>"' });
|
|
||||||
expect($config.find('uniqel').size()).to.be(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('exposes an object a config object using it\'s name', function () {
|
|
||||||
var $config = build(
|
|
||||||
{
|
|
||||||
'config-template': '"<uniqel>{{ controller.name }}</uniqel>"',
|
|
||||||
'config-object': 'controller',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controller: {
|
|
||||||
name: 'foobar'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
expect($config.find('uniqel').text()).to.be('foobar');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only renders the config-template once', function () {
|
|
||||||
var $config = build({ 'config-template': '"<div render-counter></div>"' });
|
|
||||||
expect($config.find('[render-counter]').text()).to.be('1');
|
|
||||||
});
|
|
||||||
});
|
|
55
src/ui/public/directives/__tests__/kbn_top_nav.js
Normal file
55
src/ui/public/directives/__tests__/kbn_top_nav.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import ngMock from 'ng_mock';
|
||||||
|
import expect from 'expect.js';
|
||||||
|
import { assign } from 'lodash';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import navbarExtensionsRegistry from 'ui/registry/navbar_extensions';
|
||||||
|
import Registry from 'ui/registry/_registry';
|
||||||
|
import 'ui/navbar_extensions';
|
||||||
|
|
||||||
|
describe('kbnTopNav Directive', function () {
|
||||||
|
|
||||||
|
var build = function () {};
|
||||||
|
let $testScope = null;
|
||||||
|
let stubRegistry;
|
||||||
|
|
||||||
|
beforeEach(ngMock.module('kibana', function ($compileProvider, PrivateProvider) {
|
||||||
|
var renderCount = 0;
|
||||||
|
$compileProvider.directive('renderCounter', function () {
|
||||||
|
return {
|
||||||
|
link: function ($scope, $el) {
|
||||||
|
$el.html(++renderCount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
stubRegistry = new Registry({
|
||||||
|
index: ['name'],
|
||||||
|
group: ['appName'],
|
||||||
|
order: ['order']
|
||||||
|
});
|
||||||
|
|
||||||
|
PrivateProvider.swap(navbarExtensionsRegistry, stubRegistry);
|
||||||
|
ngMock.module('kibana/navbar');
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(ngMock.inject(function ($compile, $rootScope) {
|
||||||
|
build = function (scopeVars) {
|
||||||
|
var $el = $('<kbn-top-nav name="foo">');
|
||||||
|
$testScope = $rootScope.$new();
|
||||||
|
assign($testScope, scopeVars || {});
|
||||||
|
$compile($el)($testScope);
|
||||||
|
$testScope.$digest();
|
||||||
|
return $el;
|
||||||
|
};
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('sets the proper functions on the kbnTopNav prop on scope', function () {
|
||||||
|
var $config = build();
|
||||||
|
expect($testScope.kbnTopNav.open).to.be.a(Function);
|
||||||
|
expect($testScope.kbnTopNav.close).to.be.a(Function);
|
||||||
|
expect($testScope.kbnTopNav.is).to.be.a(Function);
|
||||||
|
expect($testScope.kbnTopNav.toggle).to.be.a(Function);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,82 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import 'ui/watch_multi';
|
|
||||||
import ConfigTemplate from 'ui/config_template';
|
|
||||||
import angular from 'angular';
|
|
||||||
import 'ui/directives/input_focus';
|
|
||||||
import uiModules from 'ui/modules';
|
|
||||||
var module = uiModules.get('kibana');
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* config directive
|
|
||||||
*
|
|
||||||
* Creates a full width horizonal config section, usually under a nav/subnav.
|
|
||||||
* ```
|
|
||||||
* <config config-template="configTemplate" config-object="configurable"></config>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.directive('config', function ($compile) {
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
scope: {
|
|
||||||
configTemplate: '=',
|
|
||||||
configClose: '=',
|
|
||||||
configSubmit: '=',
|
|
||||||
configObject: '='
|
|
||||||
},
|
|
||||||
link: function ($scope, element, attr) {
|
|
||||||
var tmpScope = $scope.$new();
|
|
||||||
|
|
||||||
$scope.$watch('configObject', function (newVal) {
|
|
||||||
$scope[attr.configObject] = $scope.configObject;
|
|
||||||
});
|
|
||||||
|
|
||||||
var wrapTmpl = function (tmpl) {
|
|
||||||
if ($scope.configSubmit) {
|
|
||||||
return '<form role="form" class="container-fluid" ng-submit="configSubmit()">' + tmpl + '</form>';
|
|
||||||
} else {
|
|
||||||
return '<div class="container-fluid">' + tmpl + '</div>';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.$watchMulti([
|
|
||||||
'configSubmit',
|
|
||||||
'configTemplate.current || configTemplate'
|
|
||||||
], function () {
|
|
||||||
var tmpl = $scope.configTemplate;
|
|
||||||
if (tmpl instanceof ConfigTemplate) {
|
|
||||||
tmpl = tmpl.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpScope.$destroy();
|
|
||||||
tmpScope = $scope.$new();
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
if (tmpl) {
|
|
||||||
html = $compile('' +
|
|
||||||
'<div class="config" ng-show="configTemplate">' +
|
|
||||||
wrapTmpl(tmpl) +
|
|
||||||
' <div class="config-close remove">' +
|
|
||||||
' <i class="fa fa-chevron-circle-up" ng-click="close()"></i>' +
|
|
||||||
' </div>' +
|
|
||||||
'</div>' +
|
|
||||||
''
|
|
||||||
)(tmpScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.html(html);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$scope.close = function () {
|
|
||||||
if (_.isFunction($scope.configClose)) $scope.configClose();
|
|
||||||
if ($scope.configTemplate instanceof ConfigTemplate) {
|
|
||||||
$scope.configTemplate.current = null;
|
|
||||||
} else {
|
|
||||||
$scope.configTemplate = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
116
src/ui/public/directives/kbn_top_nav.js
Normal file
116
src/ui/public/directives/kbn_top_nav.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
import 'ui/watch_multi';
|
||||||
|
import angular from 'angular';
|
||||||
|
import 'ui/directives/input_focus';
|
||||||
|
import uiModules from 'ui/modules';
|
||||||
|
var module = uiModules.get('kibana');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kbnTopNav directive
|
||||||
|
*
|
||||||
|
* The top section that shows the timepicker, load, share and save dialogues.
|
||||||
|
* ```
|
||||||
|
* <kbn-top-nav name="current-app-for-extensions" config="path.to.menuItems"></kbn-top-nav>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.directive('kbnTopNav', function (Private) {
|
||||||
|
const filterTemplate = require('ui/chrome/config/filter.html');
|
||||||
|
const intervalTemplate = require('ui/chrome/config/interval.html');
|
||||||
|
function optionsNormalizer(defaultFunction, opt) {
|
||||||
|
if (!opt.key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _.assign({
|
||||||
|
label: _.capitalize(opt.key),
|
||||||
|
hasFunction: !!opt.run,
|
||||||
|
description: ('Toggle ' + opt.key),
|
||||||
|
run: defaultFunction
|
||||||
|
}, opt);
|
||||||
|
}
|
||||||
|
function getTemplatesMap(configs) {
|
||||||
|
const templateMap = {};
|
||||||
|
configs.forEach(conf => {
|
||||||
|
if (conf.template) {
|
||||||
|
templateMap[conf.key] = conf.template;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return templateMap;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
transclude: true,
|
||||||
|
template: function ($el, $attrs) {
|
||||||
|
// This is ugly
|
||||||
|
// This is necessary because of navbar-extensions
|
||||||
|
// It will no accept any programatic way of setting its name
|
||||||
|
// besides this because it happens so early in the digest cycle
|
||||||
|
return `
|
||||||
|
<navbar class="kibana-nav-options">
|
||||||
|
<div ng-transclude></div>
|
||||||
|
<div class="button-group kibana-nav-actions" role="toolbar">
|
||||||
|
<button
|
||||||
|
ng-repeat="menuItem in kbnTopNav.menuItems"
|
||||||
|
aria-label="{{::menuItem.description}}"
|
||||||
|
aria-haspopup="{{!menuItem.hasFunction}}"
|
||||||
|
aria-expanded="{{kbnTopNav.is(menuItem.key)}}"
|
||||||
|
ng-class="{active: kbnTopNav.is(menuItem.key)}"
|
||||||
|
ng-click="menuItem.run(menuItem)"
|
||||||
|
ng-bind="menuItem.label">
|
||||||
|
</button>
|
||||||
|
<navbar-extensions name="${$attrs.name}"></navbar-extensions>
|
||||||
|
</div>
|
||||||
|
<kbn-global-timepicker></kbn-global-timepicker>
|
||||||
|
</navbar>
|
||||||
|
<div class="config" ng-show="kbnTopNav.currTemplate">
|
||||||
|
<div id="template_wrapper" class="container-fluid"></div>
|
||||||
|
<div class="config-close remove">
|
||||||
|
<i class="fa fa-chevron-circle-up" ng-click="kbnTopNav.close()"></i>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
},
|
||||||
|
controller: ['$scope', '$compile', '$attrs', function ($scope, $compile, $attrs) {
|
||||||
|
const ctrlObj = this;
|
||||||
|
// toggleCurrTemplate(false) to turn it off
|
||||||
|
ctrlObj.toggleCurrTemplate = function (which) {
|
||||||
|
if (ctrlObj.curr === which || !which) {
|
||||||
|
ctrlObj.curr = null;
|
||||||
|
} else {
|
||||||
|
ctrlObj.curr = which;
|
||||||
|
}
|
||||||
|
const templateToCompile = ctrlObj.templates[ctrlObj.curr] || false;
|
||||||
|
$scope.kbnTopNav.currTemplate = templateToCompile ? $compile(templateToCompile)($scope) : false;
|
||||||
|
};
|
||||||
|
const normalizeOpts = _.partial(optionsNormalizer, (item) => {
|
||||||
|
ctrlObj.toggleCurrTemplate(item.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
const niceMenuItems = _.compact(($scope[$attrs.config] || []).map(normalizeOpts));
|
||||||
|
ctrlObj.templates = _.assign({
|
||||||
|
interval: intervalTemplate,
|
||||||
|
filter: filterTemplate,
|
||||||
|
}, getTemplatesMap(niceMenuItems));
|
||||||
|
|
||||||
|
|
||||||
|
$scope.kbnTopNav = {
|
||||||
|
menuItems: niceMenuItems,
|
||||||
|
currTemplate: false,
|
||||||
|
is: which => { return ctrlObj.curr === which; },
|
||||||
|
close: () => { ctrlObj.toggleCurrTemplate(false); },
|
||||||
|
toggle: ctrlObj.toggleCurrTemplate,
|
||||||
|
open: which => {
|
||||||
|
if (ctrlObj.curr !== which) {
|
||||||
|
ctrlObj.toggleCurrTemplate(which);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}],
|
||||||
|
link: function ($scope, element, attr, configCtrl) {
|
||||||
|
$scope.$watch('kbnTopNav.currTemplate', newVal => {
|
||||||
|
element.find('#template_wrapper').html(newVal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,140 +0,0 @@
|
||||||
import ngMock from 'ng_mock';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
import expect from 'expect.js';
|
|
||||||
import angular from 'angular';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import navbarExtensionsRegistry from 'ui/registry/navbar_extensions';
|
|
||||||
import Registry from 'ui/registry/_registry';
|
|
||||||
import 'ui/navbar';
|
|
||||||
|
|
||||||
const defaultMarkup = `
|
|
||||||
<navbar name="testing">
|
|
||||||
<div class="button-group" role="toolbar">
|
|
||||||
<button>
|
|
||||||
<i aria-hidden="true" class="fa fa-file-new-o"></i>
|
|
||||||
</button>
|
|
||||||
<button>
|
|
||||||
<i aria-hidden="true" class="fa fa-save"></i>
|
|
||||||
</button>
|
|
||||||
<button>
|
|
||||||
<i aria-hidden="true" class="fa fa-folder-open-o"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</navbar>`;
|
|
||||||
|
|
||||||
|
|
||||||
describe('navbar directive', function () {
|
|
||||||
let $rootScope;
|
|
||||||
let $compile;
|
|
||||||
let stubRegistry;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
ngMock.module('kibana', function (PrivateProvider) {
|
|
||||||
stubRegistry = new Registry({
|
|
||||||
index: ['name'],
|
|
||||||
group: ['appName'],
|
|
||||||
order: ['order']
|
|
||||||
});
|
|
||||||
|
|
||||||
PrivateProvider.swap(navbarExtensionsRegistry, stubRegistry);
|
|
||||||
});
|
|
||||||
|
|
||||||
ngMock.module('kibana/navbar');
|
|
||||||
|
|
||||||
// Create the scope
|
|
||||||
ngMock.inject(function ($injector) {
|
|
||||||
$rootScope = $injector.get('$rootScope');
|
|
||||||
$compile = $injector.get('$compile');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function init(markup = defaultMarkup) {
|
|
||||||
// Give us a scope
|
|
||||||
const $el = angular.element(markup);
|
|
||||||
$compile($el)($rootScope);
|
|
||||||
$el.scope().$digest();
|
|
||||||
return $el;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('incorrect use', function () {
|
|
||||||
it('should throw if missing a name property', function () {
|
|
||||||
const markup = `<navbar><div class="button-group" role="toolbar"></div></navbar>`;
|
|
||||||
expect(() => init(markup)).to.throwException(/requires a name attribute/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if missing a button group', function () {
|
|
||||||
const markup = `<navbar name="testing"></navbar>`;
|
|
||||||
expect(() => init(markup)).to.throwException(/must have exactly 1 button group/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if multiple button groups', function () {
|
|
||||||
const markup = ` <navbar name="testing">
|
|
||||||
<div class="button-group" role="toolbar">
|
|
||||||
<button>
|
|
||||||
<i aria-hidden="true" class="fa fa-file-new-o"></i>
|
|
||||||
</button>
|
|
||||||
<button>
|
|
||||||
<i aria-hidden="true" class="fa fa-save"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="button-group" role="toolbar">
|
|
||||||
<button>
|
|
||||||
<i aria-hidden="true" class="fa fa-folder-open-o"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</navbar>`;
|
|
||||||
expect(() => init(markup)).to.throwException(/must have exactly 1 button group/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if button group not direct child', function () {
|
|
||||||
const markup = `<navbar><div><div class="button-group" role="toolbar"></div></div></navbar>`;
|
|
||||||
expect(() => init(markup)).to.throwException(/must have exactly 1 button group/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('injecting extensions', function () {
|
|
||||||
function registerExtension(def = {}) {
|
|
||||||
stubRegistry.register(function () {
|
|
||||||
return _.defaults(def, {
|
|
||||||
name: 'exampleButton',
|
|
||||||
appName: 'testing',
|
|
||||||
order: 0,
|
|
||||||
template: `
|
|
||||||
<button class="test-button">
|
|
||||||
<i aria-hidden="true" class="fa fa-rocket"></i>
|
|
||||||
</button>`
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should use the default markup', function () {
|
|
||||||
var $el = init();
|
|
||||||
expect($el.find('.button-group button').length).to.equal(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append to end then order == 0', function () {
|
|
||||||
registerExtension({ order: 0 });
|
|
||||||
var $el = init();
|
|
||||||
|
|
||||||
expect($el.find('.button-group button').length).to.equal(4);
|
|
||||||
expect($el.find('.button-group button').last().hasClass('test-button')).to.be.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append to end then order > 0', function () {
|
|
||||||
registerExtension({ order: 1 });
|
|
||||||
var $el = init();
|
|
||||||
|
|
||||||
expect($el.find('.button-group button').length).to.equal(4);
|
|
||||||
expect($el.find('.button-group button').last().hasClass('test-button')).to.be.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append to end then order < 0', function () {
|
|
||||||
registerExtension({ order: -1 });
|
|
||||||
var $el = init();
|
|
||||||
|
|
||||||
expect($el.find('.button-group button').length).to.equal(4);
|
|
||||||
expect($el.find('.button-group button').first().hasClass('test-button')).to.be.ok();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
106
src/ui/public/navbar_extensions/__tests__/navbar_extensions.js
Normal file
106
src/ui/public/navbar_extensions/__tests__/navbar_extensions.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import ngMock from 'ng_mock';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import expect from 'expect.js';
|
||||||
|
import angular from 'angular';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import navbarExtensionsRegistry from 'ui/registry/navbar_extensions';
|
||||||
|
import Registry from 'ui/registry/_registry';
|
||||||
|
import 'ui/navbar_extensions';
|
||||||
|
|
||||||
|
const defaultMarkup = `
|
||||||
|
<navbar-extensions name="testing"></navbar-extensions>`;
|
||||||
|
|
||||||
|
|
||||||
|
describe('navbar-extensions directive', function () {
|
||||||
|
let $rootScope;
|
||||||
|
let $compile;
|
||||||
|
let stubRegistry;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
ngMock.module('kibana', function (PrivateProvider) {
|
||||||
|
stubRegistry = new Registry({
|
||||||
|
index: ['name'],
|
||||||
|
group: ['appName'],
|
||||||
|
order: ['order']
|
||||||
|
});
|
||||||
|
|
||||||
|
PrivateProvider.swap(navbarExtensionsRegistry, stubRegistry);
|
||||||
|
});
|
||||||
|
|
||||||
|
ngMock.module('kibana/navbar');
|
||||||
|
|
||||||
|
// Create the scope
|
||||||
|
ngMock.inject(function ($injector) {
|
||||||
|
$rootScope = $injector.get('$rootScope');
|
||||||
|
$compile = $injector.get('$compile');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function init(markup = defaultMarkup) {
|
||||||
|
// Give us a scope
|
||||||
|
const $el = angular.element(markup);
|
||||||
|
$compile($el)($rootScope);
|
||||||
|
$el.scope().$digest();
|
||||||
|
return $el;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('incorrect use', function () {
|
||||||
|
it('should throw if missing a name property', function () {
|
||||||
|
const markup = `<navbar-extensions><div class="button-group" role="toolbar"></div></navbar-extensions>`;
|
||||||
|
expect(() => init(markup)).to.throwException(/requires a name attribute/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('injecting extensions', function () {
|
||||||
|
function registerExtension(def = {}) {
|
||||||
|
stubRegistry.register(function () {
|
||||||
|
return _.defaults(def, {
|
||||||
|
name: 'exampleButton',
|
||||||
|
appName: 'testing',
|
||||||
|
order: 0,
|
||||||
|
template: `
|
||||||
|
<button class="test-button">
|
||||||
|
<i aria-hidden="true" class="fa fa-rocket"></i>
|
||||||
|
</button>`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should append to end then order == 0', function () {
|
||||||
|
registerExtension({ order: 0 });
|
||||||
|
var $el = init();
|
||||||
|
|
||||||
|
expect($el.find('button').last().hasClass('test-button')).to.be.ok();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enforce the order prop', function () {
|
||||||
|
registerExtension({
|
||||||
|
order: 1,
|
||||||
|
template: `
|
||||||
|
<button class="test-button-1">
|
||||||
|
<i aria-hidden="true" class="fa fa-rocket"></i>
|
||||||
|
</button>`
|
||||||
|
});
|
||||||
|
registerExtension({
|
||||||
|
order: 2,
|
||||||
|
template: `
|
||||||
|
<button class="test-button-2">
|
||||||
|
<i aria-hidden="true" class="fa fa-rocket"></i>
|
||||||
|
</button>`
|
||||||
|
});
|
||||||
|
registerExtension({
|
||||||
|
order: 0,
|
||||||
|
template: `
|
||||||
|
<button class="test-button-0">
|
||||||
|
<i aria-hidden="true" class="fa fa-rocket"></i>
|
||||||
|
</button>`
|
||||||
|
});
|
||||||
|
var $el = init();
|
||||||
|
|
||||||
|
expect($el.find('button').length).to.equal(3);
|
||||||
|
expect($el.find('button').last().hasClass('test-button-2')).to.be.ok();
|
||||||
|
expect($el.find('button').first().hasClass('test-button-0')).to.be.ok();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -6,7 +6,7 @@ import uiModules from 'ui/modules';
|
||||||
const navbar = uiModules.get('kibana/navbar');
|
const navbar = uiModules.get('kibana/navbar');
|
||||||
|
|
||||||
|
|
||||||
navbar.directive('navbar', function (Private, $compile) {
|
navbar.directive('navbarExtensions', function (Private, $compile) {
|
||||||
const navbarExtensions = Private(RegistryNavbarExtensionsProvider);
|
const navbarExtensions = Private(RegistryNavbarExtensionsProvider);
|
||||||
const getExtensions = _.memoize(function (name) {
|
const getExtensions = _.memoize(function (name) {
|
||||||
if (!name) throw new Error('navbar directive requires a name attribute');
|
if (!name) throw new Error('navbar directive requires a name attribute');
|
||||||
|
@ -16,36 +16,20 @@ navbar.directive('navbar', function (Private, $compile) {
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
template: function ($el, $attrs) {
|
template: function ($el, $attrs) {
|
||||||
const $buttonGroup = $el.children('.button-group');
|
|
||||||
if ($buttonGroup.length !== 1) throw new Error('navbar must have exactly 1 button group');
|
|
||||||
|
|
||||||
const extensions = getExtensions($attrs.name);
|
const extensions = getExtensions($attrs.name);
|
||||||
const buttons = $buttonGroup.children().detach().toArray();
|
const controls = extensions.map(function (extension, i) {
|
||||||
const controls = [
|
|
||||||
...buttons.map(function (button) {
|
|
||||||
return {
|
|
||||||
order: 0,
|
|
||||||
$el: $(button),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...extensions.map(function (extension, i) {
|
|
||||||
return {
|
return {
|
||||||
order: extension.order,
|
order: extension.order,
|
||||||
index: i,
|
index: i,
|
||||||
extension: extension,
|
extension: extension,
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
];
|
|
||||||
|
|
||||||
_.sortBy(controls, 'order').forEach(function (control) {
|
_.sortBy(controls, 'order').forEach(function (control) {
|
||||||
if (control.$el) {
|
|
||||||
return $buttonGroup.append(control.$el);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { extension, index } = control;
|
const { extension, index } = control;
|
||||||
const $ext = $(`<render-directive definition="navbar.extensions[${index}]"></render-directive>`);
|
const $ext = $(`<render-directive definition="navbar.extensions[${index}]"></render-directive>`);
|
||||||
$ext.html(extension.template);
|
$ext.html(extension.template);
|
||||||
$buttonGroup.append($ext);
|
$el.append($ext);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $el.html();
|
return $el.html();
|
|
@ -1,8 +0,0 @@
|
||||||
<div class="config" ng-show="configTemplate">
|
|
||||||
<form role="form" class="container-fluid" ng-submit="configSubmit()">
|
|
||||||
<div ng-bind-template="{{configTemplate}}" />
|
|
||||||
</form>
|
|
||||||
<div class="config-close remove">
|
|
||||||
<i class="fa fa-chevron-circle-up" ng-click="close()"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue