diff --git a/package.json b/package.json index 9227ccf9378f..bc312afd8524 100644 --- a/package.json +++ b/package.json @@ -24,18 +24,19 @@ "license": "Apache-2.0", "author": "Rashid Khan ", "contributors": [ - "Spencer Alger ", - "Matt Bargar ", - "Jon Budzenski ", "Chris Cowan ", "Court Ewing ", + "Jim Unger ", "Joe Fleming ", + "Jon Budzenski ", + "Juan Thomassie ", "Khalah Jones-Golden ", "Lukas Olson ", - "Juan Thomassie ", + "Matt Bargar ", + "Nicolás Bevacqua ", "Shelby Sturgis ", - "Tim Sullivan ", - "Jim Unger " + "Spencer Alger ", + "Tim Sullivan " ], "scripts": { "test": "grunt test", diff --git a/src/ui/public/chrome/__tests__/_tab_fake_store.js b/src/ui/public/chrome/__tests__/fixtures/stub_browser_storage.js similarity index 73% rename from src/ui/public/chrome/__tests__/_tab_fake_store.js rename to src/ui/public/chrome/__tests__/fixtures/stub_browser_storage.js index a678440d14b7..812cd85ce4d7 100644 --- a/src/ui/public/chrome/__tests__/_tab_fake_store.js +++ b/src/ui/public/chrome/__tests__/fixtures/stub_browser_storage.js @@ -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() ]; } diff --git a/src/ui/public/chrome/__tests__/tab.js b/src/ui/public/chrome/__tests__/tab.js index add635bf7e2c..cc3d3ab1b4ba 100644 --- a/src/ui/public/chrome/__tests__/tab.js +++ b/src/ui/public/chrome/__tests__/tab.js @@ -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'); diff --git a/src/ui/public/chrome/__tests__/tab_collection.js b/src/ui/public/chrome/__tests__/tab_collection.js index 419e00a0fa32..1bfa9c20b8f3 100644 --- a/src/ui/public/chrome/__tests__/tab_collection.js +++ b/src/ui/public/chrome/__tests__/tab_collection.js @@ -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([ diff --git a/src/ui/public/chrome/api/__tests__/angular.js b/src/ui/public/chrome/api/__tests__/angular.js index 51b77b57df0a..4f93d284a692 100644 --- a/src/ui/public/chrome/api/__tests__/angular.js +++ b/src/ui/public/chrome/api/__tests__/angular.js @@ -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', () => { diff --git a/src/ui/public/chrome/api/__tests__/apps.js b/src/ui/public/chrome/api/__tests__/apps.js index e50fe59e57ef..273635b9cc41 100644 --- a/src/ui/public/chrome/api/__tests__/apps.js +++ b/src/ui/public/chrome/api/__tests__/apps.js @@ -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'); diff --git a/src/ui/public/chrome/api/__tests__/nav.js b/src/ui/public/chrome/api/__tests__/nav.js index 2161c63d6ce7..8dffa7e4eebb 100644 --- a/src/ui/public/chrome/api/__tests__/nav.js +++ b/src/ui/public/chrome/api/__tests__/nav.js @@ -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); + }); + }); }); diff --git a/src/ui/public/chrome/api/nav.js b/src/ui/public/chrome/api/nav.js index d993baa216ef..0a3611c47c8d 100644 --- a/src/ui/public/chrome/api/nav.js +++ b/src/ui/public/chrome/api/nav.js @@ -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); + } } }; diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index 823a34acc51f..dc748f5d5256 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -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'); diff --git a/test/support/pages/settings_page.js b/test/support/pages/settings_page.js index ca0168129b3a..e330e92e5387 100644 --- a/test/support/pages/settings_page.js +++ b/test/support/pages/settings_page.js @@ -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')