mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -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",
|
||||
"author": "Rashid Khan <rashid.khan@elastic.co>",
|
||||
"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>",
|
||||
"Court Ewing <court@elastic.co>",
|
||||
"Jim Unger <jim.unger@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>",
|
||||
"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>",
|
||||
"Tim Sullivan <tim@elastic.co>",
|
||||
"Jim Unger <jim.unger@elastic.co>"
|
||||
"Spencer Alger <spencer.alger@elastic.co>",
|
||||
"Tim Sullivan <tim@elastic.co>"
|
||||
],
|
||||
"scripts": {
|
||||
"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';
|
||||
|
||||
module.exports = class ClusterManager {
|
||||
constructor(opts, settings) {
|
||||
constructor(opts = {}, settings = {}) {
|
||||
this.log = new Log(opts.quiet, opts.silent);
|
||||
this.addedCount = 0;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import _ from 'lodash';
|
||||
import cluster from 'cluster';
|
||||
let { resolve } = require('path');
|
||||
let { EventEmitter } = require('events');
|
||||
import { resolve } from 'path';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { BinderFor, fromRoot } from '../../utils';
|
||||
|
||||
let cliPath = fromRoot('src/cli');
|
||||
let baseArgs = _.difference(process.argv.slice(2), ['--no-watch']);
|
||||
|
@ -18,13 +18,6 @@ let dead = fork => {
|
|||
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 {
|
||||
constructor(opts) {
|
||||
opts = opts || {};
|
||||
|
@ -36,26 +29,33 @@ module.exports = class Worker extends EventEmitter {
|
|||
this.watch = (opts.watch !== false);
|
||||
this.startCount = 0;
|
||||
this.online = false;
|
||||
this.listening = false;
|
||||
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 || []);
|
||||
this.env = {
|
||||
kbnWorkerType: this.type,
|
||||
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) {
|
||||
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
|
||||
this.fork = null;
|
||||
this.forkBinder = null;
|
||||
|
||||
this.online = false;
|
||||
this.listening = false;
|
||||
this.emit('fork:exit');
|
||||
|
||||
if (code) {
|
||||
this.log.bad(`${this.title} crashed`, 'with status code', code);
|
||||
|
@ -72,26 +72,48 @@ module.exports = class Worker extends EventEmitter {
|
|||
this.start();
|
||||
}
|
||||
|
||||
shutdown() {
|
||||
async shutdown() {
|
||||
if (this.fork && !dead(this.fork)) {
|
||||
kill(this.fork);
|
||||
this.fork.removeListener('message', this.onMessage);
|
||||
this.fork.removeListener('online', this.onOnline);
|
||||
this.fork.removeListener('disconnect', this.onDisconnect);
|
||||
// kill the fork
|
||||
this.fork.process.kill();
|
||||
this.fork.killed = true;
|
||||
|
||||
// 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) {
|
||||
if (!_.isArray(msg) || msg[0] !== 'WORKER_BROADCAST') return;
|
||||
this.emit('broadcast', msg[1]);
|
||||
parseIncomingMessage(msg) {
|
||||
if (!_.isArray(msg)) return;
|
||||
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() {
|
||||
this.online = true;
|
||||
this.emit('fork:online');
|
||||
}
|
||||
|
||||
onDisconnect() {
|
||||
this.online = false;
|
||||
this.listening = false;
|
||||
}
|
||||
|
||||
flushChangeBuffer() {
|
||||
|
@ -102,9 +124,13 @@ module.exports = class Worker extends EventEmitter {
|
|||
}, '');
|
||||
}
|
||||
|
||||
start() {
|
||||
// once "exit" event is received with 0 status, start() is called again
|
||||
if (this.fork) return this.shutdown();
|
||||
async start() {
|
||||
if (this.fork) {
|
||||
// once "exit" event is received with 0 status, start() is called again
|
||||
this.shutdown();
|
||||
await new Promise(cb => this.once('online', cb));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.changes.length) {
|
||||
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.on('message', this.onMessage);
|
||||
this.fork.on('online', this.onOnline);
|
||||
this.fork.on('disconnect', this.onDisconnect);
|
||||
this.forkBinder = new BinderFor(this.fork);
|
||||
|
||||
// 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) {
|
||||
return action.call(this, function (...args) {
|
||||
var ret = fn.apply(this, args);
|
||||
let ret = fn.apply(this, args);
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
ret.then(null, function (e) {
|
||||
console.log('FATAL CLI ERROR', e.stack);
|
||||
|
|
|
@ -69,6 +69,6 @@ ${indent(cmd.optionHelp(), 2)}
|
|||
}
|
||||
|
||||
function humanReadableArgName(arg) {
|
||||
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||
let nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import fs from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
|
||||
let legacySettingMap = {
|
||||
// server
|
||||
|
|
|
@ -3,7 +3,7 @@ const { isWorker } = require('cluster');
|
|||
const { resolve } = require('path');
|
||||
|
||||
const cwd = process.cwd();
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
|
||||
let canCluster;
|
||||
try {
|
||||
|
|
|
@ -23,7 +23,7 @@ program
|
|||
.command('help <command>')
|
||||
.description('get the help for a specific command')
|
||||
.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}`);
|
||||
cmd.help();
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ program
|
|||
});
|
||||
|
||||
// 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) {
|
||||
program.defaultHelp();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import path from 'path';
|
||||
import expect from 'expect.js';
|
||||
import fromRoot from '../../../utils/from_root';
|
||||
import { fromRoot } from '../../../utils';
|
||||
import { resolve } from 'path';
|
||||
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 Logger from '../lib/logger';
|
||||
import pkg from '../../utils/package_json';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
import KbnServer from '../../server/kbn_server';
|
||||
import readYamlConfig from '../../cli/serve/read_yaml_config';
|
||||
import { versionSatisfies, cleanVersion } from './version';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
import list from './list';
|
||||
import Logger from '../lib/logger';
|
||||
import { parse } from './settings';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
import remove from './remove';
|
||||
import Logger from '../lib/logger';
|
||||
import { parse } from './settings';
|
||||
|
|
|
@ -17,23 +17,23 @@ export default function GeoHashGridAggResponseFixture() {
|
|||
// },
|
||||
// });
|
||||
|
||||
var geoHashCharts = _.union(
|
||||
let geoHashCharts = _.union(
|
||||
_.range(48, 57), // 0-9
|
||||
_.range(65, 90), // 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
|
||||
var docCount = 0;
|
||||
var buckets = _.times(_.random(40, 200), function () {
|
||||
let docCount = 0;
|
||||
let buckets = _.times(_.random(40, 200), function () {
|
||||
return _.sample(geoHashCharts, 3).join('');
|
||||
})
|
||||
.sort()
|
||||
.map(function (geoHash) {
|
||||
var count = _.random(1, 5000);
|
||||
let count = _.random(1, 5000);
|
||||
|
||||
totalDocCount += count;
|
||||
docCount += count;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var results = {};
|
||||
let results = {};
|
||||
|
||||
results.timeSeries = {
|
||||
data: {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var data = { };
|
||||
let data = { };
|
||||
|
||||
data.metricOnly = {
|
||||
hits: { total: 1000, hits: [], max_score: 0 },
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
var longString = Array(200).join('_');
|
||||
let longString = Array(200).join('_');
|
||||
|
||||
export default function (id, mapping) {
|
||||
function fakeVals(type) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
function stubbedLogstashFields() {
|
||||
var sourceData = [
|
||||
let sourceData = [
|
||||
{ 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: '@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';
|
||||
|
||||
export default function (Private, Promise) {
|
||||
var indexPatterns = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
var getIndexPatternStub = sinon.stub();
|
||||
let indexPatterns = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
let getIndexPatternStub = sinon.stub();
|
||||
getIndexPatternStub.returns(Promise.resolve(indexPatterns));
|
||||
|
||||
var courier = {
|
||||
let courier = {
|
||||
indexPatterns: { get: getIndexPatternStub },
|
||||
getStub: getIndexPatternStub
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
var keys = {};
|
||||
let keys = {};
|
||||
export default {
|
||||
get: function (path, def) {
|
||||
return keys[path] == null ? def : keys[path];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
|
||||
function stubbedDocSourceResponse(Private) {
|
||||
var mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
let mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
|
||||
return function (id, index) {
|
||||
index = index || '.kibana';
|
||||
|
|
|
@ -3,21 +3,21 @@ import TestUtilsStubIndexPatternProvider from 'test_utils/stub_index_pattern';
|
|||
import IndexPatternsFieldTypesProvider from 'ui/index_patterns/_field_types';
|
||||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
export default function stubbedLogstashIndexPatternService(Private) {
|
||||
var StubIndexPattern = Private(TestUtilsStubIndexPatternProvider);
|
||||
var fieldTypes = Private(IndexPatternsFieldTypesProvider);
|
||||
var mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
let StubIndexPattern = Private(TestUtilsStubIndexPatternProvider);
|
||||
let fieldTypes = Private(IndexPatternsFieldTypesProvider);
|
||||
let mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
|
||||
|
||||
var fields = mockLogstashFields.map(function (field) {
|
||||
let fields = mockLogstashFields.map(function (field) {
|
||||
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 (!_.has(field, 'sortable')) field.sortable = type.sortable;
|
||||
if (!_.has(field, 'filterable')) field.filterable = type.filterable;
|
||||
return field;
|
||||
});
|
||||
|
||||
var indexPattern = new StubIndexPattern('logstash-*', 'time', fields);
|
||||
let indexPattern = new StubIndexPattern('logstash-*', 'time', fields);
|
||||
indexPattern.id = 'logstash-*';
|
||||
|
||||
return indexPattern;
|
||||
|
|
|
@ -3,8 +3,8 @@ import searchResponse from 'fixtures/search_response';
|
|||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
|
||||
export default function stubSearchSource(Private, $q, Promise) {
|
||||
var deferedResult = $q.defer();
|
||||
var indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
let deferedResult = $q.defer();
|
||||
let indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
|
||||
return {
|
||||
sort: sinon.spy(),
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import $ from 'jquery';
|
||||
import VislibVisProvider from 'ui/vislib/vis';
|
||||
|
||||
var $visCanvas = $('<div>')
|
||||
let $visCanvas = $('<div>')
|
||||
.attr('id', 'vislib-vis-fixtures')
|
||||
.css({
|
||||
height: '500px',
|
||||
|
@ -15,8 +15,8 @@ var $visCanvas = $('<div>')
|
|||
})
|
||||
.appendTo('body');
|
||||
|
||||
var count = 0;
|
||||
var visHeight = $visCanvas.height();
|
||||
let count = 0;
|
||||
let visHeight = $visCanvas.height();
|
||||
|
||||
$visCanvas.new = function () {
|
||||
count += 1;
|
||||
|
@ -32,7 +32,7 @@ afterEach(function () {
|
|||
|
||||
module.exports = function VislibFixtures(Private) {
|
||||
return function (visLibParams) {
|
||||
var Vis = Private(VislibVisProvider);
|
||||
let Vis = Private(VislibVisProvider);
|
||||
return new Vis($visCanvas.new(), _.defaults({}, visLibParams || {}, {
|
||||
shareYAxis: true,
|
||||
addTooltip: true,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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) {
|
||||
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
var cloneDeep = require('lodash').cloneDeep;
|
||||
var fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
||||
// this file is not transpiled
|
||||
'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) {
|
||||
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import LazyServer from './lazy_server';
|
||||
import LazyOptimizer from './lazy_optimizer';
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
|
||||
export default async (kbnServer, kibanaHapiServer, config) => {
|
||||
let server = new LazyServer(
|
||||
|
|
|
@ -26,7 +26,7 @@ docViewsRegistry.register(function () {
|
|||
};
|
||||
|
||||
$scope.showArrayInObjectsWarning = function (row, field) {
|
||||
var value = $scope.flattened[field];
|
||||
let value = $scope.flattened[field];
|
||||
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 FilterBarPushFilterProvider from 'ui/filter_bar/push_filter';
|
||||
import tileMapTemplate from 'plugins/kbn_vislib_vis_types/editors/tile_map.html';
|
||||
|
||||
export default function TileMapVisType(Private, getAppState, courier, config) {
|
||||
const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider);
|
||||
const Schemas = Private(VisSchemasProvider);
|
||||
|
@ -120,6 +121,8 @@ export default function TileMapVisType(Private, getAppState, courier, config) {
|
|||
group: 'buckets',
|
||||
name: 'split',
|
||||
title: 'Split Chart',
|
||||
deprecate: true,
|
||||
deprecateMessage: 'The Split Chart feature for Tile Maps has been deprecated.',
|
||||
min: 0,
|
||||
max: 1
|
||||
}
|
||||
|
|
|
@ -1,60 +1,11 @@
|
|||
<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">
|
||||
<span ng-show="dash.id" class="kibana-nav-info-title">
|
||||
<span ng-bind="::dash.title"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</kbn-top-nav>
|
||||
|
||||
<navbar ng-show="chrome.getVisible()" name="dashboard-search">
|
||||
<form name="queryInput"
|
||||
|
@ -91,7 +42,7 @@
|
|||
|
||||
<div ng-show="!state.panels.length" class="text-center start-screen">
|
||||
<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>
|
||||
|
||||
<dashboard-grid></dashboard-grid>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import angular from 'angular';
|
||||
import ConfigTemplate from 'ui/config_template';
|
||||
import chrome from 'ui/chrome';
|
||||
import 'ui/directives/config';
|
||||
import 'ui/directives/kbn_top_nav';
|
||||
import 'ui/courier';
|
||||
import 'ui/config';
|
||||
import 'ui/notify';
|
||||
import 'ui/typeahead';
|
||||
import 'ui/navbar';
|
||||
import 'ui/navbar_extensions';
|
||||
import 'ui/share';
|
||||
import 'plugins/kibana/dashboard/directives/grid';
|
||||
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.configTemplate = new ConfigTemplate({
|
||||
save: require('plugins/kibana/dashboard/partials/save_dashboard.html'),
|
||||
load: require('plugins/kibana/dashboard/partials/load_dashboard.html'),
|
||||
share: require('plugins/kibana/dashboard/partials/share.html'),
|
||||
pickVis: require('plugins/kibana/dashboard/partials/pick_visualization.html'),
|
||||
options: require('plugins/kibana/dashboard/partials/options.html'),
|
||||
filter: require('ui/chrome/config/filter.html'),
|
||||
interval: require('ui/chrome/config/interval.html')
|
||||
});
|
||||
$scope.topNavMenu = [{
|
||||
key: 'new',
|
||||
description: 'New Dashboard',
|
||||
run: function () { kbnUrl.change('/dashboard', {}); },
|
||||
}, {
|
||||
key: 'add',
|
||||
description: 'Add a panel to the dashboard',
|
||||
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');
|
||||
|
||||
|
@ -198,7 +209,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
|||
|
||||
dash.save()
|
||||
.then(function (id) {
|
||||
$scope.configTemplate.close('save');
|
||||
$scope.kbnTopNav.close('save');
|
||||
if (id) {
|
||||
notify.info('Saved Dashboard as "' + dash.title + '"');
|
||||
if (dash.id !== $routeParams.id) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
import ConfigTemplate from 'ui/config_template';
|
||||
import getSort from 'ui/doc_table/lib/get_sort';
|
||||
import rison from 'ui/utils/rison';
|
||||
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.showInterval = !$scope.showInterval;
|
||||
};
|
||||
// config panel templates
|
||||
$scope.configTemplate = new ConfigTemplate({
|
||||
load: require('plugins/kibana/discover/partials/load_search.html'),
|
||||
save: require('plugins/kibana/discover/partials/save_search.html'),
|
||||
share: require('plugins/kibana/discover/partials/share_search.html'),
|
||||
filter: require('ui/chrome/config/filter.html'),
|
||||
interval: require('ui/chrome/config/interval.html')
|
||||
});
|
||||
$scope.topNavMenu = [{
|
||||
key: 'new',
|
||||
description: 'New Search',
|
||||
run: function () { kbnUrl.change('/discover'); }
|
||||
}, {
|
||||
key: 'save',
|
||||
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;
|
||||
|
||||
|
||||
|
@ -287,7 +295,7 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
|||
|
||||
return savedSearch.save()
|
||||
.then(function (id) {
|
||||
$scope.configTemplate.close('save');
|
||||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
notify.info('Saved Data Source "' + savedSearch.title + '"');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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">
|
||||
<span ng-show="opts.savedSearch.id" class="kibana-nav-info-title">
|
||||
<span ng-bind="::opts.savedSearch.title"></span>
|
||||
|
@ -9,44 +9,7 @@
|
|||
<strong class="discover-info-hits">{{(hits || 0) | number:0}}</strong>
|
||||
<ng-pluralize count="hits" when="{'1':'hit', 'other':'hits'}"></ng-pluralize>
|
||||
</div>
|
||||
<div class="kibana-nav-actions button-group" role="toolbar">
|
||||
<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>
|
||||
</kbn-top-nav>
|
||||
<navbar name="discover-search">
|
||||
<form role="form" class="fill inline-form" ng-submit="fetch()" name="discoverSearch">
|
||||
<div class="typeahead" kbn-typeahead="discover">
|
||||
|
@ -116,7 +79,7 @@
|
|||
<div ng-show="opts.timefield">
|
||||
<p>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'plugins/kibana/discover/saved_searches/saved_searches';
|
||||
import 'plugins/kibana/discover/directives/timechart';
|
||||
import 'ui/navbar';
|
||||
import 'ui/navbar_extensions';
|
||||
import 'ui/collapsible_sidebar';
|
||||
import 'plugins/kibana/discover/components/field_chooser/field_chooser';
|
||||
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...">
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -40,12 +40,11 @@ uiModules
|
|||
},
|
||||
link: function ($scope, $el) {
|
||||
timefilter.enabled = false;
|
||||
$scope.sections = sections;
|
||||
$scope.sections = sections.inOrder;
|
||||
$scope.section = _.find($scope.sections, { name: $scope.sectionName });
|
||||
|
||||
$scope.sections.forEach(function (section) {
|
||||
section.class = (section === $scope.section) ? 'active' : void 0;
|
||||
$scope.sections.forEach(section => {
|
||||
section.class = section === $scope.section ? 'active' : undefined;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<label>Select {{ groupName }} type</label>
|
||||
<ul class="form-group list-group list-group-menu">
|
||||
<li
|
||||
ng-hide="schema.deprecate"
|
||||
ng-repeat="schema in availableSchema"
|
||||
ng-click="add.submit(schema)"
|
||||
class="list-group-item list-group-menu-item">
|
||||
|
@ -18,10 +19,10 @@
|
|||
class="vis-editor-agg-wide-btn">
|
||||
|
||||
<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 }}
|
||||
</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 }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,6 +33,7 @@ uiModules
|
|||
$scope.schemas.forEach(function (schema) {
|
||||
stats.min += schema.min;
|
||||
stats.max += schema.max;
|
||||
stats.deprecate = schema.deprecate;
|
||||
});
|
||||
|
||||
$scope.availableSchema = $scope.schemas.filter(function (schema) {
|
||||
|
|
|
@ -10,4 +10,13 @@
|
|||
style="display: none;">
|
||||
</div>
|
||||
|
||||
<!-- schema editors get added down here: aggSelect.html, agg_types/controls/*.html -->
|
||||
<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 -->
|
||||
|
|
|
@ -1,60 +1,12 @@
|
|||
<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">
|
||||
<span ng-show="savedVis.id" class="vis-editor-info-title">
|
||||
<span ng-bind="::savedVis.title"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
</kbn-top-nav>
|
||||
<navbar ng-if="chrome.getVisible()" name="visualize-search">
|
||||
<div class="fill bitty-modal-container">
|
||||
<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/editor/sidebar';
|
||||
import 'plugins/kibana/visualize/editor/agg_filter';
|
||||
import 'ui/navbar';
|
||||
import 'ui/navbar_extensions';
|
||||
import 'ui/visualize';
|
||||
import 'ui/collapsible_sidebar';
|
||||
import 'ui/share';
|
||||
import angular from 'angular';
|
||||
import ConfigTemplate from 'ui/config_template';
|
||||
import Notifier from 'ui/notify/notifier';
|
||||
import RegistryVisTypesProvider from 'ui/registry/vis_types';
|
||||
import DocTitleProvider from 'ui/doc_title';
|
||||
|
@ -80,14 +79,27 @@ uiModules
|
|||
|
||||
const searchSource = savedVis.searchSource;
|
||||
|
||||
// config panel templates
|
||||
const configTemplate = new ConfigTemplate({
|
||||
save: require('plugins/kibana/visualize/editor/panels/save.html'),
|
||||
load: require('plugins/kibana/visualize/editor/panels/load.html'),
|
||||
share: require('plugins/kibana/visualize/editor/panels/share.html'),
|
||||
filter: require('ui/chrome/config/filter.html'),
|
||||
interval: require('ui/chrome/config/interval.html')
|
||||
});
|
||||
$scope.topNavMenu = [{
|
||||
key: 'new',
|
||||
description: 'New Visualization',
|
||||
run: function () { kbnUrl.change('/visualize', {}); }
|
||||
}, {
|
||||
key: 'save',
|
||||
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) {
|
||||
docTitle.change(savedVis.title);
|
||||
|
@ -129,7 +141,6 @@ uiModules
|
|||
$scope.uiState = $state.makeStateful('uiState');
|
||||
$scope.timefilter = timefilter;
|
||||
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter');
|
||||
$scope.configTemplate = configTemplate;
|
||||
|
||||
editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state);
|
||||
editableVis.listeners.brush = vis.listeners.brush = brushEvent;
|
||||
|
@ -235,7 +246,7 @@ uiModules
|
|||
|
||||
savedVis.save()
|
||||
.then(function (id) {
|
||||
configTemplate.close('save');
|
||||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
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 { resolve } from 'path';
|
||||
import { map, fromNode } from 'bluebird';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { union } from 'lodash';
|
||||
|
||||
import findSourceFiles from './find_source_files';
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
|
||||
export default (kibana) => {
|
||||
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)
|
||||
*
|
||||
* var schema = { ... }
|
||||
* let schema = { ... }
|
||||
* new Config(schema);
|
||||
*
|
||||
*/
|
||||
|
||||
var data = {
|
||||
let data = {
|
||||
test: {
|
||||
hosts: ['host-01', 'host-02'],
|
||||
client: {
|
||||
|
@ -25,7 +25,7 @@ var data = {
|
|||
}
|
||||
};
|
||||
|
||||
var schema = Joi.object({
|
||||
let schema = Joi.object({
|
||||
test: Joi.object({
|
||||
enable: Joi.boolean().default(true),
|
||||
hosts: Joi.array().items(Joi.string()),
|
||||
|
@ -44,39 +44,39 @@ describe('lib/config/config', function () {
|
|||
describe('constructor', function () {
|
||||
|
||||
it('should not allow any config if the schema is not passed', function () {
|
||||
var config = new Config();
|
||||
var run = function () {
|
||||
let config = new Config();
|
||||
let run = function () {
|
||||
config.set('something.enable', true);
|
||||
};
|
||||
expect(run).to.throwException();
|
||||
});
|
||||
|
||||
it('should allow keys in the schema', function () {
|
||||
var config = new Config(schema);
|
||||
var run = function () {
|
||||
let config = new Config(schema);
|
||||
let run = function () {
|
||||
config.set('test.client.host', 'http://0.0.0.0');
|
||||
};
|
||||
expect(run).to.not.throwException();
|
||||
});
|
||||
|
||||
it('should not allow keys not in the schema', function () {
|
||||
var config = new Config(schema);
|
||||
var run = function () {
|
||||
let config = new Config(schema);
|
||||
let run = function () {
|
||||
config.set('paramNotDefinedInTheSchema', true);
|
||||
};
|
||||
expect(run).to.throwException();
|
||||
});
|
||||
|
||||
it('should not allow child keys not in the schema', function () {
|
||||
var config = new Config(schema);
|
||||
var run = function () {
|
||||
let config = new Config(schema);
|
||||
let run = function () {
|
||||
config.set('test.client.paramNotDefinedInTheSchema', true);
|
||||
};
|
||||
expect(run).to.throwException();
|
||||
});
|
||||
|
||||
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.client.type')).to.be('datastore');
|
||||
});
|
||||
|
@ -92,7 +92,7 @@ describe('lib/config/config', function () {
|
|||
|
||||
it('should reset the config object with new values', function () {
|
||||
config.set(data);
|
||||
var newData = config.get();
|
||||
let newData = config.get();
|
||||
newData.test.enable = false;
|
||||
config.resetTo(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 () {
|
||||
var hosts = ['host-01', 'host-02'];
|
||||
let hosts = ['host-01', 'host-02'];
|
||||
config.set({ test: { enable: false, hosts: hosts } });
|
||||
expect(config.get('test.enable')).to.be(false);
|
||||
expect(config.get('test.hosts')).to.eql(hosts);
|
||||
});
|
||||
|
||||
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 });
|
||||
expect(config.get('test.enable')).to.be(false);
|
||||
expect(config.get('test.hosts')).to.eql(hosts);
|
||||
});
|
||||
|
||||
it('should override values with just the values present', function () {
|
||||
var newData = _.cloneDeep(data);
|
||||
let newData = _.cloneDeep(data);
|
||||
config.set(data);
|
||||
newData.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) {
|
||||
var run = function () {
|
||||
let run = function () {
|
||||
config.set('test.enable', 'something');
|
||||
};
|
||||
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 () {
|
||||
var newData = _.cloneDeep(data);
|
||||
let newData = _.cloneDeep(data);
|
||||
newData.test.enable = true;
|
||||
expect(config.get()).to.eql(newData);
|
||||
});
|
||||
|
@ -194,14 +194,14 @@ describe('lib/config/config', function () {
|
|||
});
|
||||
|
||||
it('should throw exception for unknown config values', function () {
|
||||
var run = function () {
|
||||
let run = function () {
|
||||
config.get('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 () {
|
||||
var run = function getUndefValue() {
|
||||
let run = function getUndefValue() {
|
||||
config.get('test.undefValue');
|
||||
};
|
||||
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 () {
|
||||
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||
let newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||
config.extendSchema('myTest', newSchema);
|
||||
expect(config.get('myTest.test')).to.be(true);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(config.get('prefix')).to.eql({ myTest: { 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 () {
|
||||
var newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||
var run = function () {
|
||||
let newSchema = Joi.object({ test: Joi.boolean().default(true) }).default();
|
||||
let run = function () {
|
||||
config.extendSchema('test', newSchema);
|
||||
};
|
||||
expect(run).to.throwException();
|
||||
|
@ -241,7 +241,7 @@ describe('lib/config/config', function () {
|
|||
|
||||
describe('#removeSchema(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)
|
||||
}));
|
||||
|
||||
|
@ -251,7 +251,7 @@ describe('lib/config/config', 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');
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import expect from 'expect.js';
|
|||
describe('explode_by(dot, flatObject)', function () {
|
||||
|
||||
it('should explode a flatten object with dots', function () {
|
||||
var flatObject = {
|
||||
let flatObject = {
|
||||
'test.enable': true,
|
||||
'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 () {
|
||||
var flatObject = {
|
||||
let flatObject = {
|
||||
'test/enable': true,
|
||||
'test/hosts': ['host-01', 'host-02']
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import expect from 'expect.js';
|
|||
describe('flatten_with(dot, nestedObj)', function () {
|
||||
|
||||
it('should flatten object with dot', function () {
|
||||
var nestedObj = {
|
||||
let nestedObj = {
|
||||
test: {
|
||||
enable: true,
|
||||
hosts: ['host-01', 'host-02'],
|
||||
|
|
|
@ -4,7 +4,7 @@ import expect from 'expect.js';
|
|||
describe('override(target, source)', function () {
|
||||
|
||||
it('should override the values form source to target', function () {
|
||||
var target = {
|
||||
let target = {
|
||||
test: {
|
||||
enable: true,
|
||||
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({
|
||||
test: {
|
||||
enable: true,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import _ from 'lodash';
|
||||
module.exports = function (dot, flatObject) {
|
||||
var fullObject = {};
|
||||
let fullObject = {};
|
||||
_.each(flatObject, function (value, key) {
|
||||
var keys = key.split(dot);
|
||||
let keys = key.split(dot);
|
||||
(function walk(memo, keys, value) {
|
||||
var _key = keys.shift();
|
||||
let _key = keys.shift();
|
||||
if (keys.length === 0) {
|
||||
memo[_key] = value;
|
||||
} else {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
module.exports = function (dot, nestedObj, flattenArrays) {
|
||||
let key; // original key
|
||||
var stack = []; // track key stack
|
||||
var flatObj = {};
|
||||
let stack = []; // track key stack
|
||||
let flatObj = {};
|
||||
(function flattenObj(obj) {
|
||||
_.keys(obj).forEach(function (key) {
|
||||
stack.push(key);
|
||||
|
|
|
@ -3,8 +3,8 @@ import flattenWith from './flatten_with';
|
|||
import explodeBy from './explode_by';
|
||||
|
||||
module.exports = function (target, source) {
|
||||
var _target = flattenWith('.', target);
|
||||
var _source = flattenWith('.', source);
|
||||
let _target = flattenWith('.', target);
|
||||
let _source = flattenWith('.', source);
|
||||
return explodeBy('.', _.defaults(_source, _target));
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { get } from 'lodash';
|
|||
import { randomBytes } from 'crypto';
|
||||
import os from 'os';
|
||||
|
||||
import fromRoot from '../../utils/from_root';
|
||||
import { fromRoot } from '../../utils';
|
||||
|
||||
module.exports = () => Joi.object({
|
||||
pkg: Joi.object({
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Hapi from 'hapi';
|
||||
import { constant, once, compact, flatten } from 'lodash';
|
||||
import { promisify, resolve, fromNode } from 'bluebird';
|
||||
import fromRoot from '../utils/from_root';
|
||||
import pkg from '../utils/package_json';
|
||||
import { isWorker } from 'cluster';
|
||||
import { fromRoot, pkg } from '../utils';
|
||||
|
||||
let rootDir = fromRoot('.');
|
||||
|
||||
|
@ -78,6 +78,11 @@ module.exports = class KbnServer {
|
|||
await this.ready();
|
||||
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}`);
|
||||
return server;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import expect from 'expect.js';
|
|||
|
||||
describe('applyFiltersToKeys(obj, actionsByKey)', function () {
|
||||
it('applies for each key+prop in actionsByKey', function () {
|
||||
var data = applyFiltersToKeys({
|
||||
let data = applyFiltersToKeys({
|
||||
a: {
|
||||
b: {
|
||||
c: 1
|
||||
|
|
|
@ -21,7 +21,7 @@ function apply(obj, key, action) {
|
|||
obj[k] = ('' + val).replace(/./g, 'X');
|
||||
}
|
||||
else if (/\/.+\//.test(action)) {
|
||||
var matches = action.match(/\/(.+)\//);
|
||||
let matches = action.match(/\/(.+)\//);
|
||||
if (matches) {
|
||||
let regex = new RegExp(matches[1]);
|
||||
obj[k] = ('' + val).replace(regex, replacer);
|
||||
|
|
|
@ -41,13 +41,13 @@ module.exports = class TransformObjStream extends Stream.Transform {
|
|||
}
|
||||
|
||||
_transform(event, enc, next) {
|
||||
var data = this.filter(this.readEvent(event));
|
||||
let data = this.filter(this.readEvent(event));
|
||||
this.push(this.format(data) + '\n');
|
||||
next();
|
||||
}
|
||||
|
||||
readEvent(event) {
|
||||
var data = {
|
||||
let data = {
|
||||
type: event.event,
|
||||
'@timestamp': moment.utc(event.timestamp).format(),
|
||||
tags: [].concat(event.tags || []),
|
||||
|
@ -69,7 +69,7 @@ module.exports = class TransformObjStream extends Stream.Transform {
|
|||
referer: event.source.referer
|
||||
};
|
||||
|
||||
var contentLength = 0;
|
||||
let contentLength = 0;
|
||||
if (typeof event.responsePayload === 'object') {
|
||||
contentLength = stringify(event.responsePayload).length;
|
||||
} else {
|
||||
|
@ -82,7 +82,7 @@ module.exports = class TransformObjStream extends Stream.Transform {
|
|||
contentLength: contentLength
|
||||
};
|
||||
|
||||
var query = querystring.stringify(event.query);
|
||||
let query = querystring.stringify(event.query);
|
||||
if (query) data.req.url += '?' + query;
|
||||
|
||||
|
||||
|
|
|
@ -2,19 +2,19 @@ import _ from 'lodash';
|
|||
import Boom from 'boom';
|
||||
import Promise from 'bluebird';
|
||||
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) {
|
||||
var path = config.get('pid.file');
|
||||
let path = config.get('pid.file');
|
||||
if (!path) return;
|
||||
|
||||
var pid = String(process.pid);
|
||||
let pid = String(process.pid);
|
||||
|
||||
return writeFile(path, pid, { flag: 'wx' })
|
||||
.catch(function (err) {
|
||||
if (err.code !== 'EEXIST') throw err;
|
||||
|
||||
var log = {
|
||||
let log = {
|
||||
tmpl: 'pid file already exists at <%= path %>',
|
||||
path: path,
|
||||
pid: pid
|
||||
|
@ -36,7 +36,7 @@ module.exports = Promise.method(function (kbnServer, server, config) {
|
|||
pid: pid
|
||||
});
|
||||
|
||||
var clean = _.once(function (code) {
|
||||
let clean = _.once(function (code) {
|
||||
unlink(path);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { each } from 'bluebird';
|
|||
import PluginCollection from './plugin_collection';
|
||||
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 pluginPaths = [].concat(config.get('plugins.paths') || []);
|
||||
|
|
|
@ -17,21 +17,21 @@ describe('ServerStatus class', function () {
|
|||
|
||||
describe('#create(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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get(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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getState(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();
|
||||
expect(serverStatus.getState('name')).to.be('green');
|
||||
});
|
||||
|
@ -39,11 +39,11 @@ describe('ServerStatus class', function () {
|
|||
|
||||
describe('#overall()', 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');
|
||||
|
||||
var match = function (overall, state) {
|
||||
let match = function (overall, state) {
|
||||
expect(overall).to.have.property('state', state.id);
|
||||
expect(overall).to.have.property('title', state.title);
|
||||
expect(overall).to.have.property('icon', state.icon);
|
||||
|
@ -65,20 +65,20 @@ describe('ServerStatus class', function () {
|
|||
|
||||
describe('#toJSON()', function () {
|
||||
it('serializes to overall status and individuals', function () {
|
||||
var one = serverStatus.create('one');
|
||||
var two = serverStatus.create('two');
|
||||
var three = serverStatus.create('three');
|
||||
let one = serverStatus.create('one');
|
||||
let two = serverStatus.create('two');
|
||||
let three = serverStatus.create('three');
|
||||
|
||||
one.green();
|
||||
two.yellow();
|
||||
three.red();
|
||||
|
||||
var obj = JSON.parse(JSON.stringify(serverStatus));
|
||||
let obj = JSON.parse(JSON.stringify(serverStatus));
|
||||
expect(obj).to.have.property('overall');
|
||||
expect(obj.overall.state).to.eql(serverStatus.overall().state);
|
||||
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.two).to.have.property('state', 'yellow');
|
||||
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) {
|
||||
var status = serverStatus.create('test');
|
||||
let status = serverStatus.create('test');
|
||||
|
||||
status.once('change', function (prev, prevMsg) {
|
||||
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 () {
|
||||
var status = serverStatus.create('test');
|
||||
var stub = sinon.stub();
|
||||
let status = serverStatus.create('test');
|
||||
let stub = sinon.stub();
|
||||
status.on('change', stub);
|
||||
status.green('Ready');
|
||||
status.green('Ready');
|
||||
|
@ -50,17 +50,17 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it('should create a JSON representation of the status', function () {
|
||||
var status = serverStatus.create('test');
|
||||
let status = serverStatus.create('test');
|
||||
status.green('Ready');
|
||||
|
||||
var json = status.toJSON();
|
||||
let json = status.toJSON();
|
||||
expect(json.state).to.eql('green');
|
||||
expect(json.message).to.eql('Ready');
|
||||
});
|
||||
|
||||
it('should call on handler if status is already matched', function (done) {
|
||||
var status = serverStatus.create('test');
|
||||
var msg = 'Test Ready';
|
||||
let status = serverStatus.create('test');
|
||||
let msg = 'Test Ready';
|
||||
status.green(msg);
|
||||
|
||||
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) {
|
||||
var status = serverStatus.create('test');
|
||||
var msg = 'Test Ready';
|
||||
let status = serverStatus.create('test');
|
||||
let msg = 'Test Ready';
|
||||
status.green(msg);
|
||||
|
||||
status.once('green', function (prev, prevMsg) {
|
||||
|
@ -88,16 +88,16 @@ describe('Status class', function () {
|
|||
|
||||
function testState(color) {
|
||||
it(`should change the state to ${color} when #${color}() is called`, function () {
|
||||
var status = serverStatus.create('test');
|
||||
var message = 'testing ' + color;
|
||||
let status = serverStatus.create('test');
|
||||
let message = 'testing ' + color;
|
||||
status[color](message);
|
||||
expect(status).to.have.property('state', color);
|
||||
expect(status).to.have.property('message', message);
|
||||
});
|
||||
|
||||
it(`should trigger the "change" listner when #${color}() is called`, function (done) {
|
||||
var status = serverStatus.create('test');
|
||||
var message = 'testing ' + color;
|
||||
let status = serverStatus.create('test');
|
||||
let message = 'testing ' + color;
|
||||
status.on('change', function (prev, prevMsg) {
|
||||
expect(status.state).to.be(color);
|
||||
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) {
|
||||
var status = serverStatus.create('test');
|
||||
var message = 'testing ' + color;
|
||||
let status = serverStatus.create('test');
|
||||
let message = 'testing ' + color;
|
||||
status.on(color, function (prev, prevMsg) {
|
||||
expect(status.state).to.be(color);
|
||||
expect(status.message).to.be(message);
|
||||
|
|
|
@ -22,8 +22,8 @@ module.exports = function (kbnServer, server, config) {
|
|||
});
|
||||
|
||||
server.decorate('reply', 'renderStatusPage', function () {
|
||||
var app = kbnServer.uiExports.getHiddenApp('status_page');
|
||||
var resp = app ? this.renderApp(app) : this(kbnServer.status.toString());
|
||||
let app = kbnServer.uiExports.getHiddenApp('status_page');
|
||||
let resp = app ? this.renderApp(app) : this(kbnServer.status.toString());
|
||||
resp.code(kbnServer.status.isGreen() ? 200 : 503);
|
||||
return resp;
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ module.exports = function (kbnServer, server, config) {
|
|||
let secSinceLast = (now - lastReport) / 1000;
|
||||
lastReport = now;
|
||||
|
||||
var port = config.get('server.port');
|
||||
let port = config.get('server.port');
|
||||
let requests = _.get(event, ['requests', port, 'total'], 0);
|
||||
let requestsPerSecond = requests / secSinceLast;
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ function Samples(max) {
|
|||
}
|
||||
|
||||
Samples.prototype.add = function (sample) {
|
||||
var vals = this.vals;
|
||||
var length = this.length = Math.min(this.length + 1, this.max);
|
||||
let vals = this.vals;
|
||||
let length = this.length = Math.min(this.length + 1, this.max);
|
||||
|
||||
_.forOwn(sample, function (val, name) {
|
||||
if (val == null) val = null;
|
||||
|
|
|
@ -31,15 +31,15 @@ module.exports = class ServerStatus {
|
|||
}
|
||||
|
||||
overall() {
|
||||
var state = _(this._created)
|
||||
let state = _(this._created)
|
||||
.map(function (status) {
|
||||
return states.get(status.state);
|
||||
})
|
||||
.sortBy('severity')
|
||||
.pop();
|
||||
|
||||
var statuses = _.where(this._created, { state: state.id });
|
||||
var since = _.get(_.sortBy(statuses, 'since'), [0, 'since']);
|
||||
let statuses = _.where(this._created, { state: state.id });
|
||||
let since = _.get(_.sortBy(statuses, 'since'), [0, 'since']);
|
||||
|
||||
return {
|
||||
state: state.id,
|
||||
|
@ -59,7 +59,7 @@ module.exports = class ServerStatus {
|
|||
}
|
||||
|
||||
toString() {
|
||||
var overall = this.overall();
|
||||
let overall = this.overall();
|
||||
return `${overall.title} – ${overall.nickname}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class Status extends EventEmitter {
|
|||
|
||||
this.on('change', function (previous, previousMsg) {
|
||||
this.since = new Date();
|
||||
var tags = ['status', name];
|
||||
let tags = ['status', name];
|
||||
tags.push(this.state === 'red' ? 'error' : 'info');
|
||||
|
||||
server.log(tags, {
|
||||
|
|
|
@ -8,7 +8,7 @@ Bluebird.longStackTraces();
|
|||
* replace the Promise service with Bluebird so that tests
|
||||
* 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 () {
|
||||
* beforeEach(noDigestPromises.activate);
|
||||
|
@ -16,7 +16,7 @@ Bluebird.longStackTraces();
|
|||
* });
|
||||
*/
|
||||
|
||||
var active = false;
|
||||
let active = false;
|
||||
|
||||
uiModules
|
||||
.get('kibana')
|
||||
|
|
|
@ -2,8 +2,8 @@ import $ from 'jquery';
|
|||
import _ from 'lodash';
|
||||
import Promise from 'bluebird';
|
||||
import keyMap from 'ui/utils/key_map';
|
||||
var reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1));
|
||||
var KeyboardEvent = window.KeyboardEvent;
|
||||
let reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1));
|
||||
let KeyboardEvent = window.KeyboardEvent;
|
||||
|
||||
/**
|
||||
* Simulate keyboard events in an element. This allows testing the way that
|
||||
|
@ -35,7 +35,7 @@ var KeyboardEvent = window.KeyboardEvent;
|
|||
* @async
|
||||
*/
|
||||
export default function ($el, sequence) {
|
||||
var modifierState = {
|
||||
let modifierState = {
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
altKey: false,
|
||||
|
@ -45,7 +45,7 @@ export default function ($el, sequence) {
|
|||
return doList(_.clone(sequence));
|
||||
|
||||
function setModifier(key, state) {
|
||||
var name = key + 'Key';
|
||||
let name = key + 'Key';
|
||||
if (modifierState.hasOwnProperty(name)) {
|
||||
modifierState[name] = !!state;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export default function ($el, sequence) {
|
|||
return Promise.try(function () {
|
||||
if (!list || !list.length) return;
|
||||
|
||||
var event = list[0];
|
||||
let event = list[0];
|
||||
if (_.isString(event)) {
|
||||
event = { type: 'press', key: event };
|
||||
}
|
||||
|
@ -91,14 +91,14 @@ export default function ($el, sequence) {
|
|||
}
|
||||
|
||||
function fire(type, key, repeat) {
|
||||
var keyCode = reverseKeyMap[key];
|
||||
let keyCode = reverseKeyMap[key];
|
||||
if (!keyCode) throw new TypeError('invalid key "' + key + '"');
|
||||
|
||||
if (type === 'keydown') setModifier(key, true);
|
||||
if (type === 'keyup') setModifier(key, false);
|
||||
|
||||
var $target = _.isFunction($el) ? $el() : $el;
|
||||
var $event = new $.Event(type, _.defaults({ keyCode: keyCode }, modifierState));
|
||||
let $target = _.isFunction($el) ? $el() : $el;
|
||||
let $event = new $.Event(type, _.defaults({ keyCode: keyCode }, modifierState));
|
||||
$target.trigger($event);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,10 +9,10 @@ import RegistryFieldFormatsProvider from 'ui/registry/field_formats';
|
|||
import IndexPatternsFlattenHitProvider from 'ui/index_patterns/_flatten_hit';
|
||||
import IndexPatternsFieldProvider from 'ui/index_patterns/_field';
|
||||
export default function (Private) {
|
||||
var fieldFormats = Private(RegistryFieldFormatsProvider);
|
||||
var flattenHit = Private(IndexPatternsFlattenHitProvider);
|
||||
let fieldFormats = Private(RegistryFieldFormatsProvider);
|
||||
let flattenHit = Private(IndexPatternsFlattenHitProvider);
|
||||
|
||||
var Field = Private(IndexPatternsFieldProvider);
|
||||
let Field = Private(IndexPatternsFieldProvider);
|
||||
|
||||
function StubIndexPattern(pattern, timeField, fields) {
|
||||
this.id = pattern;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'angular';
|
||||
import 'ui/chrome';
|
||||
import 'ui/chrome/context';
|
||||
import 'ui/bind';
|
||||
import 'ui/bound_to_config_obj';
|
||||
import 'ui/config';
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
const store = Symbol('store');
|
||||
|
||||
export default class TabFakeStore {
|
||||
export default class StubBrowserStorage {
|
||||
constructor() { this[store] = new Map(); }
|
||||
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); }
|
||||
getKeys() { return [ ...this[store].keys() ]; }
|
||||
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 Tab from '../tab';
|
||||
import expect from 'expect.js';
|
||||
import TabFakeStore from './_tab_fake_store';
|
||||
import StubBrowserStorage from './fixtures/stub_browser_storage';
|
||||
|
||||
describe('Chrome Tab', function () {
|
||||
describe('construction', function () {
|
||||
|
@ -88,7 +89,7 @@ describe('Chrome Tab', function () {
|
|||
});
|
||||
|
||||
it('discovers the lastUrl', function () {
|
||||
const lastUrlStore = new TabFakeStore();
|
||||
const lastUrlStore = new StubBrowserStorage();
|
||||
const tab = new Tab({ id: 'foo', lastUrlStore });
|
||||
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 () {
|
||||
const lastUrlStore = new TabFakeStore();
|
||||
const lastUrlStore = new StubBrowserStorage();
|
||||
const tab = new Tab({ id: 'foo', baseUrl: '/bar', lastUrlStore });
|
||||
tab.setLastUrl('/bar/foo/1');
|
||||
|
||||
|
@ -114,7 +115,7 @@ describe('Chrome Tab', function () {
|
|||
|
||||
describe('#setLastUrl()', 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 });
|
||||
|
||||
expect(tab.lastUrl).to.not.equal('foo');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
import TabFakeStore from './_tab_fake_store';
|
||||
import StubBrowserStorage from './fixtures/stub_browser_storage';
|
||||
import TabCollection from '../tab_collection';
|
||||
import Tab from '../tab';
|
||||
import { indexBy, random } from 'lodash';
|
||||
|
@ -54,7 +54,7 @@ describe('Chrome TabCollection', function () {
|
|||
|
||||
describe('#consumeRouteUpdate()', function () {
|
||||
it('updates the active tab', function () {
|
||||
const store = new TabFakeStore();
|
||||
const store = new StubBrowserStorage();
|
||||
const baseUrl = `http://localhost:${random(1000, 9999)}`;
|
||||
const tabs = new TabCollection({ store, defaults: { baseUrl } });
|
||||
tabs.set([
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
import kbnAngular from '../angular';
|
||||
import TabFakeStore from '../../__tests__/_tab_fake_store';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
describe('Chrome API :: Angular', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
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('#get/setShowAppsLink()', function () {
|
||||
|
@ -147,7 +147,7 @@ describe('Chrome API :: apps', function () {
|
|||
describe('#get/setLastUrlFor()', function () {
|
||||
it('reads/writes last url from storage', function () {
|
||||
const chrome = {};
|
||||
const store = new TabFakeStore();
|
||||
const store = new StubBrowserStorage();
|
||||
setup(chrome, { appUrlStore: store });
|
||||
expect(chrome.getLastUrlFor('app')).to.equal(undefined);
|
||||
chrome.setLastUrlFor('app', 'url');
|
||||
|
|
|
@ -1,45 +1,73 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
import initChromeNavApi from 'ui/chrome/api/nav';
|
||||
import StubBrowserStorage from '../../__tests__/fixtures/stub_browser_storage';
|
||||
|
||||
const basePath = '/someBasePath';
|
||||
|
||||
function getChrome(customInternals = { basePath }) {
|
||||
function init(customInternals = { basePath }) {
|
||||
const chrome = {};
|
||||
initChromeNavApi(chrome, {
|
||||
const internals = {
|
||||
nav: [],
|
||||
...customInternals,
|
||||
});
|
||||
return chrome;
|
||||
};
|
||||
initChromeNavApi(chrome, internals);
|
||||
return { chrome, internals };
|
||||
}
|
||||
|
||||
describe('chrome nav apis', function () {
|
||||
describe('#getBasePath()', function () {
|
||||
it('returns the basePath', function () {
|
||||
const chrome = getChrome();
|
||||
const { chrome } = init();
|
||||
expect(chrome.getBasePath()).to.be(basePath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addBasePath()', function () {
|
||||
it('returns undefined when nothing is passed', function () {
|
||||
const chrome = getChrome();
|
||||
const { chrome } = init();
|
||||
expect(chrome.addBasePath()).to.be(undefined);
|
||||
});
|
||||
|
||||
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`);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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`);
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
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) {
|
||||
for (const link of internals.nav) {
|
||||
link.active = startsWith(url, link.url);
|
||||
const { appId, globalState: newGlobalState } = decodeKibanaUrl(url);
|
||||
|
||||
for (const link of internals.nav) {
|
||||
const matchingTab = find(internals.tabs, { rootUrl: link.url });
|
||||
|
||||
link.active = startsWith(url, link.url);
|
||||
if (link.active) {
|
||||
setLastUrl(link, url);
|
||||
continue;
|
||||
}
|
||||
|
||||
const matchingTab = find(internals.tabs, { rootUrl: link.url });
|
||||
if (matchingTab) {
|
||||
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 >
|
||||
<!-- TODO: These config dropdowns shouldn't be hard coded -->
|
||||
<nav class="app-links-wrapper">
|
||||
<nav class="app-links-wrapper" ng-show="chrome.getVisible()">
|
||||
<li
|
||||
ng-if="!chrome.getBrand('logo') && !chrome.getBrand('smallLogo')"
|
||||
aria-label="{{ chrome.getAppTitle() }} Logo"
|
||||
|
@ -21,28 +21,12 @@
|
|||
|
||||
<app-switcher>
|
||||
</app-switcher>
|
||||
<div class="bottom-apps hide app-links">
|
||||
<div class="app-link">
|
||||
<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 class="bottom-apps">
|
||||
<div class="chrome-actions app-links" kbn-chrome-append-nav-controls></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="app-wrapper">
|
||||
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
|
||||
<div class="app-wrapper-panel">
|
||||
<kbn-notifications list="notifList"></kbn-notifications>
|
||||
<nav
|
||||
|
@ -80,6 +64,7 @@
|
|||
</div>
|
||||
<!-- /Full navbar -->
|
||||
</nav>
|
||||
<kbn-loading-indicator></kbn-loading-indicator>
|
||||
<div class="application" ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()" ng-view></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<kbn-timepicker
|
||||
from="opts.timefilter.time.from"
|
||||
to="opts.timefilter.time.to"
|
||||
mode="opts.timefilter.time.mode"
|
||||
from="timefilter.time.from"
|
||||
to="timefilter.time.to"
|
||||
mode="timefilter.time.mode"
|
||||
active-tab="'filter'"
|
||||
interval="opts.timefilter.refreshInterval">
|
||||
interval="timefilter.refreshInterval">
|
||||
</kbn-timepicker>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<kbn-timepicker
|
||||
from="opts.timefilter.time.from"
|
||||
to="opts.timefilter.time.to"
|
||||
mode="opts.timefilter.time.mode"
|
||||
from="timefilter.time.from"
|
||||
to="timefilter.time.to"
|
||||
mode="timefilter.time.mode"
|
||||
active-tab="'interval'"
|
||||
interval="opts.timefilter.refreshInterval">
|
||||
interval="timefilter.refreshInterval">
|
||||
</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;
|
||||
background-color: #fff;
|
||||
|
||||
&.hidden-chrome { left: 0; }
|
||||
&-panel {
|
||||
.flex-parent(@shrink: 0);
|
||||
box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
|
||||
|
@ -84,7 +85,6 @@ body { overflow-x: hidden; }
|
|||
|
||||
.app-icon {
|
||||
float: left;
|
||||
filter: invert(100%);
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
font-size: 1.7em;
|
||||
|
@ -95,6 +95,11 @@ body { overflow-x: hidden; }
|
|||
> img {
|
||||
height: 18px;
|
||||
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;
|
||||
}
|
||||
img {
|
||||
filter: invert(100%);
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,6 @@ import $ from 'jquery';
|
|||
|
||||
import chromeNavControlsRegistry from 'ui/registry/chrome_nav_controls';
|
||||
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) {
|
||||
|
||||
|
@ -19,7 +13,7 @@ export default function (chrome, internals) {
|
|||
const parts = [$element.html()];
|
||||
const controls = Private(chromeNavControlsRegistry);
|
||||
|
||||
for (const control of [spinner, ...controls.inOrder]) {
|
||||
for (const control of controls.inOrder) {
|
||||
parts.unshift(
|
||||
`<!-- nav control ${control.name} -->`,
|
||||
control.template
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import 'ui/directives/config';
|
||||
import 'ui/directives/kbn_top_nav';
|
||||
|
||||
import './app_switcher';
|
||||
import kbnChromeProv from './kbn_chrome';
|
||||
import kbnChromeNavControlsProv from './append_nav_controls';
|
||||
import './kbn_loading_indicator';
|
||||
|
||||
export default function (chrome, internals) {
|
||||
kbnChromeProv(chrome, internals);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
import UiModules from 'ui/modules';
|
||||
import ConfigTemplate from 'ui/config_template';
|
||||
|
||||
export default function (chrome, internals) {
|
||||
|
||||
|
@ -46,10 +45,6 @@ export default function (chrome, internals) {
|
|||
// and some local values
|
||||
chrome.httpActive = $http.pendingRequests;
|
||||
$scope.notifList = require('ui/notify')._notifs;
|
||||
$scope.appSwitcherTemplate = new ConfigTemplate({
|
||||
switcher: '<app-switcher></app-switcher>'
|
||||
});
|
||||
|
||||
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 NormalizeSortRequestProvider from 'ui/courier/data_source/_normalize_sort_request';
|
||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import _ from 'lodash';
|
||||
|
||||
describe('SearchSource#normalizeSortRequest', function () {
|
||||
let normalizeSortRequest;
|
||||
|
@ -87,4 +88,17 @@ describe('SearchSource#normalizeSortRequest', function () {
|
|||
|
||||
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 = _.defaults({}, sortValue, defaultSortOptions);
|
||||
|
||||
if (sortField === '_score') {
|
||||
delete sortValue.unmapped_type;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
|
||||
navbar.directive('navbar', function (Private, $compile) {
|
||||
navbar.directive('navbarExtensions', function (Private, $compile) {
|
||||
const navbarExtensions = Private(RegistryNavbarExtensionsProvider);
|
||||
const getExtensions = _.memoize(function (name) {
|
||||
if (!name) throw new Error('navbar directive requires a name attribute');
|
||||
|
@ -16,36 +16,20 @@ navbar.directive('navbar', function (Private, $compile) {
|
|||
return {
|
||||
restrict: 'E',
|
||||
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 buttons = $buttonGroup.children().detach().toArray();
|
||||
const controls = [
|
||||
...buttons.map(function (button) {
|
||||
return {
|
||||
order: 0,
|
||||
$el: $(button),
|
||||
};
|
||||
}),
|
||||
...extensions.map(function (extension, i) {
|
||||
return {
|
||||
order: extension.order,
|
||||
index: i,
|
||||
extension: extension,
|
||||
};
|
||||
}),
|
||||
];
|
||||
const controls = extensions.map(function (extension, i) {
|
||||
return {
|
||||
order: extension.order,
|
||||
index: i,
|
||||
extension: extension,
|
||||
};
|
||||
});
|
||||
|
||||
_.sortBy(controls, 'order').forEach(function (control) {
|
||||
if (control.$el) {
|
||||
return $buttonGroup.append(control.$el);
|
||||
}
|
||||
|
||||
const { extension, index } = control;
|
||||
const $ext = $(`<render-directive definition="navbar.extensions[${index}]"></render-directive>`);
|
||||
$ext.html(extension.template);
|
||||
$buttonGroup.append($ext);
|
||||
$el.append($ext);
|
||||
});
|
||||
|
||||
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