Merge remote-tracking branch 'upstream/master' into kable

This commit is contained in:
Rashid Khan 2016-03-30 15:43:36 -07:00
commit 226151ebb9
29 changed files with 513 additions and 92 deletions

View file

@ -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",

View 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');
}());
}
}

View 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);
}
}
});
});

View 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');
});
});
});
});

View file

@ -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;

View file

@ -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));
}
};

View file

@ -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

View file

@ -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 {

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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(

View file

@ -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';

View file

@ -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({

View file

@ -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({

View file

@ -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;
}

View file

@ -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() ]; }

View file

@ -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');

View file

@ -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([

View file

@ -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', () => {

View file

@ -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');

View file

@ -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);
});
});
});

View file

@ -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);
}
}
};

12
src/utils/binder_for.js Normal file
View file

@ -0,0 +1,12 @@
import { Binder } from './';
export default class BinderFor extends Binder {
constructor(emitter) {
super();
this.emitter = emitter;
}
on(...args) {
super.on(this.emitter, ...args);
}
}

4
src/utils/index.js Normal file
View file

@ -0,0 +1,4 @@
export Binder from './binder';
export BinderFor from './binder_for';
export fromRoot from './from_root';
export pkg from './package_json';

View file

@ -69,7 +69,7 @@ define(function (require) {
})
.then(function selectField() {
common.debug('Field = extension');
return visualizePage.selectField('extension');
return visualizePage.selectField('extension.raw');
})
.then(function setInterval() {
common.debug('switch from Rows to Columns');

View file

@ -20,21 +20,6 @@ define(function (require) {
return common.findTestSubject('settingsNav advanced').click();
},
setAdvancedSettings: function setAdvancedSettings(propertyName, propertyValue) {
var self = this;
return common.findTestSubject('advancedSetting&' + propertyName + ' editButton')
.click()
.then(function setAdvancedSettingsClickPropertyValue(selectList) {
return self.remote
.findDisplayedByCssSelector('option[label="' + propertyValue + '"]')
.click();
})
.then(function setAdvancedSettingsClickSaveButton() {
return common.findTestSubject('advancedSetting&' + propertyName + ' saveButton')
.click();
});
},
getAdvancedSettings: function getAdvancedSettings(propertyName) {
common.debug('in setAdvancedSettings');
return common.findTestSubject('advancedSetting&' + propertyName + ' currentValue')