Skip to content

Commit

Permalink
[chrome/nav] copy global state around when the url changes
Browse files Browse the repository at this point in the history
  • Loading branch information
spalger committed Mar 28, 2016
1 parent 520f9a7 commit f0fe7f9
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -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() ]; }
Expand Down
9 changes: 5 additions & 4 deletions src/ui/public/chrome/__tests__/tab.js
Original file line number Diff line number Diff line change
@@ -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 () {
Expand Down Expand Up @@ -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');

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

Expand All @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions src/ui/public/chrome/__tests__/tab_collection.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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([
Expand Down
1 change: 0 additions & 1 deletion src/ui/public/chrome/api/__tests__/angular.js
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/ui/public/chrome/api/__tests__/apps.js
Original file line number Diff line number Diff line change
@@ -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 () {
Expand Down Expand Up @@ -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');
Expand Down
46 changes: 37 additions & 9 deletions src/ui/public/chrome/api/__tests__/nav.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
57 changes: 52 additions & 5 deletions src/ui/public/chrome/api/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = String(pathnameWithoutBasepath).match(/^\/app\/([^\/]+)(?:\/|\?|#|$)/);
if (match) return match[1];
}

function decodeKibanaUrl(url) {
const parsedUrl = parse(url);
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) {
const { appId, globalState: newGlobalState } = decodeKibanaUrl(url);

for (const link of internals.nav) {
link.active = startsWith(url, link.url);
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);
}
}
};

Expand Down

0 comments on commit f0fe7f9

Please sign in to comment.