Merge pull request #7595 from cjcenizal/chore/remove-deprecated-tabs-nav

Remove deprecated nav UI and chrome tabs API.
This commit is contained in:
CJ Cenizal 2016-07-18 14:13:02 -07:00 committed by GitHub
commit b258ddc464
13 changed files with 16 additions and 631 deletions

View file

@ -1,10 +1,6 @@
require('ace');
require('ui/chrome')
.setTabs([{
id: '',
title: 'Sense Tests'
}])
.setRootTemplate(require('./index.html'))
.setRootController(function () {
window.QUnit = require('qunit-1.10.0');

View file

@ -28,11 +28,6 @@ routes
});
chrome
.setTabDefaults({
resetWhenActive: true,
lastUrlStore: window.sessionStorage,
activeIndicatorColor: '#656a76'
})
.setRootController('kibana', function ($scope, courier, config) {
// wait for the application to finish loading
$scope.$on('application.load', function () {

View file

@ -1,254 +0,0 @@
import sinon from 'auto-release-sinon';
import Tab from '../tab';
import expect from 'expect.js';
import StubBrowserStorage from './fixtures/stub_browser_storage';
describe('Chrome Tab', function () {
describe('construction', function () {
it('accepts id, title, resetWhenActive, trackLastUrl, activeIndicatorColor, baseUrl', function () {
const tab = new Tab({
id: 'foo',
title: 'Foo App',
resetWhenActive: false,
activeIndicatorColor: true,
baseUrl: 'proto:host.domain:999'
});
expect(tab.id).to.equal('foo');
expect(tab.title).to.equal('Foo App');
expect(tab.resetWhenActive).to.equal(false);
expect(tab.activeIndicatorColor).to.equal(true);
expect(tab.rootUrl).to.equal('proto:host.domain:999/foo');
const tab2 = new Tab({
id: 'bar',
title: 'Bar App',
resetWhenActive: true,
activeIndicatorColor: false,
baseUrl: 'proto:host.domain:999/sub/#/'
});
expect(tab2.id).to.equal('bar');
expect(tab2.title).to.equal('Bar App');
expect(tab2.resetWhenActive).to.equal(true);
expect(tab2.activeIndicatorColor).to.equal(null);
expect(tab2.rootUrl).to.equal('proto:host.domain:999/sub/#/bar');
});
it('starts inactive', function () {
const tab = new Tab();
expect(tab.active).to.equal(false);
});
it('uses the id to set the rootUrl', function () {
const id = 'foo';
const tab = new Tab({ id });
expect(tab.id).to.equal(id);
expect(tab.rootUrl).to.equal(`/${id}`);
});
it('creates a regexp for matching the rootUrl', function () {
const tab = new Tab({ id: 'foo' });
expect('/foo').to.match(tab.rootRegExp);
expect('/foo/bar').to.match(tab.rootRegExp);
expect('/foo/bar/max').to.match(tab.rootRegExp);
expect('/foo?bar=baz').to.match(tab.rootRegExp);
expect('/foo/?bar=baz').to.match(tab.rootRegExp);
expect('/foo#?bar=baz').to.match(tab.rootRegExp);
expect('/foobar').to.not.match(tab.rootRegExp);
expect('site.com/foo#?bar=baz').to.not.match(tab.rootRegExp);
expect('http://site.com/foo#?bar=baz').to.not.match(tab.rootRegExp);
});
it('includes the baseUrl in the rootRegExp if specified', function () {
const tab = new Tab({
id: 'foo',
baseUrl: 'http://spiderman.com/kibana'
});
expect('http://spiderman.com/kibana/foo/bar').to.match(tab.rootRegExp);
expect('/foo').to.not.match(tab.rootRegExp);
expect('https://spiderman.com/kibana/foo/bar').to.not.match(tab.rootRegExp);
});
it('accepts a function for activeIndicatorColor', function () {
let i = 0;
const tab = new Tab({
activeIndicatorColor: function () {
return i++;
}
});
expect(tab.activeIndicatorColor).to.equal(0);
expect(tab.activeIndicatorColor).to.equal(1);
expect(tab.activeIndicatorColor).to.equal(2);
expect(tab.activeIndicatorColor).to.equal(3);
});
it('discovers the lastUrl', function () {
const lastUrlStore = new StubBrowserStorage();
const tab = new Tab({ id: 'foo', lastUrlStore });
expect(tab.lastUrl).to.not.equal('/foo/bar');
tab.setLastUrl('/foo/bar');
expect(tab.lastUrl).to.equal('/foo/bar');
const tab2 = new Tab({ id: 'foo', lastUrlStore });
expect(tab2.lastUrl).to.equal('/foo/bar');
});
it('logs a warning about last urls that do not match the rootUrl', function () {
const lastUrlStore = new StubBrowserStorage();
const tab = new Tab({ id: 'foo', baseUrl: '/bar', lastUrlStore });
tab.setLastUrl('/bar/foo/1');
const stub = sinon.stub(console, 'log');
const tab2 = new Tab({ id: 'foo', baseUrl: '/baz', lastUrlStore });
sinon.assert.calledOnce(stub);
expect(tab2.lastUrl).to.equal(null);
});
});
describe('#setLastUrl()', function () {
it('updates the lastUrl and storage value if passed a lastUrlStore', function () {
const lastUrlStore = new StubBrowserStorage();
const tab = new Tab({ id: 'foo', lastUrlStore });
expect(tab.lastUrl).to.not.equal('foo');
tab.setLastUrl('foo');
expect(tab.lastUrl).to.equal('foo');
expect(lastUrlStore.getItem(tab.lastUrlStoreKey)).to.equal('foo');
});
it('only updates lastUrl if no lastUrlStore', function () {
const tab = new Tab({ id: 'foo' });
expect(tab.lastUrl).to.equal(null);
tab.setLastUrl('foo');
expect(tab.lastUrl).to.equal('foo');
const tab2 = new Tab({ id: 'foo' });
expect(tab2.lastUrl).to.not.equal('foo');
});
});
describe('#href()', function () {
it('returns the rootUrl/id be default', function () {
const tab = new Tab({ id: 'foo' });
expect(tab.href()).to.equal(tab.rootUrl);
});
it('returns the lastUrl if tracking is on', function () {
const tab = new Tab({ id: 'foo' });
tab.setLastUrl('okay');
expect(tab.href()).to.equal('okay');
});
describe('when the tab is active', function () {
it('returns the rootUrl when resetWhenActive: true', function () {
const id = 'foo';
const resetWhenActive = true;
const tab = new Tab({ id, resetWhenActive });
tab.active = true;
expect(tab.href()).to.not.equal('butt');
expect(tab.href()).to.equal(tab.rootUrl);
});
it('or returns null when not', function () {
const tab = new Tab({ id: 'foo', resetWhenActive: false });
tab.active = true;
expect(tab.href()).to.not.equal('butt');
expect(tab.href()).to.equal(null);
});
});
});
describe('#getLastPath()', function () {
it('parses a path out of the lastUrl by removing the baseUrl', function () {
const baseUrl = 'http://local:5601/app/visualize#';
const tab = new Tab({ baseUrl });
tab.setLastUrl('http://local:5601/app/visualize#/index');
expect(tab.getLastPath()).to.equal('/index');
});
it('logs a warning if the lastUrl does not extend the root url', function () {
const baseUrl = 'http://local:5601/app/visualize#';
const tab = new Tab({ baseUrl });
sinon.stub(console, 'log');
tab.setLastUrl('http://local:5601/');
tab.getLastPath();
sinon.assert.calledOnce(console.log);// eslint-disable-line no-console
});
});
describe('updateLastUrlGlobalState', function () {
const bases = [
'http://local:5601',
'',
'weird.domain/with/subpath?path#',
'weird.domain/with/#hashpath',
];
context('with new state sets _g properly', function () {
const paths = [
[ '/', '/?_g=newState' ],
[ '/?first', '/?first=&_g=newState' ],
[ '/path?first=1&_g=afterHash', '/path?first=1&_g=newState' ],
[ '/?first=1&_g=second', '/?first=1&_g=newState' ],
[ '/?g=first', '/?g=first&_g=newState' ],
[ '/a?first=1&_g=second', '/a?first=1&_g=newState' ],
[ '/?first=1&_g=second', '/?first=1&_g=newState' ],
[ '/?first&g=second', '/?first=&g=second&_g=newState' ],
];
bases.forEach(baseUrl => {
paths.forEach(([pathFrom, pathTo]) => {
const fromUrl = `${baseUrl}${pathFrom}`;
const toUrl = `${baseUrl}${pathTo}`;
it(`${fromUrl} => ${toUrl}`, function () {
const tab = new Tab({ baseUrl });
tab.setLastUrl(fromUrl);
tab.updateLastUrlGlobalState('newState');
expect(tab.getLastUrl()).to.equal(toUrl);
});
});
});
});
context('with new empty state removes _g', function () {
const paths = [
[ '/', '/' ],
[ '/?first', '/?first=' ],
[ '/path?first=1&_g=afterHash', '/path?first=1' ],
[ '/?first=1&_g=second', '/?first=1' ],
[ '/?g=first', '/?g=first' ],
[ '/a?first=1&_g=second', '/a?first=1' ],
[ '/?first=1&_g=second', '/?first=1' ],
[ '/?first&g=second', '/?first=&g=second' ],
];
bases.forEach(baseUrl => {
paths.forEach(([pathFrom, pathTo]) => {
const fromUrl = `${baseUrl}${pathFrom}`;
const toUrl = `${baseUrl}${pathTo}`;
it(`${fromUrl}`, function () {
const tab = new Tab({ baseUrl });
tab.setLastUrl(fromUrl);
tab.updateLastUrlGlobalState();
expect(tab.getLastUrl()).to.equal(toUrl);
});
});
});
});
});
});

View file

@ -1,73 +0,0 @@
import expect from 'expect.js';
import StubBrowserStorage from './fixtures/stub_browser_storage';
import TabCollection from '../tab_collection';
import Tab from '../tab';
import { indexBy, random } from 'lodash';
describe('Chrome TabCollection', function () {
describe('empty state', function () {
it('has no tabs', function () {
const tabs = new TabCollection();
expect(tabs.get()).to.eql([]);
});
it('has no active tab', function () {
const tabs = new TabCollection();
expect(!tabs.getActive()).to.equal(true);
});
});
describe('#set()', function () {
it('consumes an ordered list of Tab specs', function () {
const tabs = new TabCollection();
tabs.set([
{ id: 'foo' },
{ id: 'bar' }
]);
const ts = tabs.get();
expect(ts.length).to.equal(2);
expect(ts[0].id).to.equal('foo');
expect(ts[1].id).to.equal('bar');
});
});
describe('#setDefaults()', function () {
it('applies the defaults used to create tabs', function () {
const tabs = new TabCollection();
tabs.setDefaults({ id: 'thing' });
tabs.set([ {} ]);
expect(tabs.get()[0].id).to.equal('thing');
});
it('recreates existing tabs with new defaults', function () {
const tabs = new TabCollection();
tabs.set([ {} ]);
expect(!tabs.get()[0].id).to.equal(true);
tabs.setDefaults({ id: 'thing' });
expect(tabs.get()[0].id).to.equal('thing');
});
});
describe('#consumeRouteUpdate()', function () {
it('updates the active tab', function () {
const store = new StubBrowserStorage();
const baseUrl = `http://localhost:${random(1000, 9999)}`;
const tabs = new TabCollection({ store, defaults: { baseUrl } });
tabs.set([
{ id: 'a' },
{ id: 'b' }
]);
tabs.consumeRouteUpdate(`${baseUrl}/a`);
const {a, b} = indexBy(tabs.get(), 'id');
expect(a.active).to.equal(true);
expect(b.active).to.equal(false);
expect(tabs.getActive()).to.equal(a);
});
});
});

View file

@ -101,19 +101,13 @@ export default function (chrome, internals) {
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;
}
if (matchingTab) {
setLastUrl(link, matchingTab.getLastUrl());
} else {
refreshLastUrl(link);
}
refreshLastUrl(link);
if (newGlobalState) {
injectNewGlobalState(link, appId, newGlobalState);

View file

@ -1,93 +0,0 @@
import _ from 'lodash';
import TabCollection from '../tab_collection';
module.exports = function (chrome, internals) {
internals.tabs = new TabCollection({
defaults: {
baseUrl: `${chrome.getAppUrl()}#/`
}
});
/**
* ui/chrome tabs API
*
* The navbar at the top of the page can be assigned links which will
* pile up on the left. Each of these links has several properties that define
* how it is rendered, how it looks when it's "active" and what happens when
* it's clicked.
*
* To define a set of tabs, pass setTabs an array of TabSpec objects,
* which are just plain objects with the following properties:
*
* id {string}
* a unique value for this tab, should match the first path segment of
* routes that are supposed to belong to this tab and is matched against the route
* everytime it changes. When clicking the tab for the first time the user will be
* sent to the '/${tab.id}' url which you can use to redirect to another url if needed
*
* title {string}
* the text the tab should show
*
* resetWhenActive {boolean}
* when the the tab is considered active, should clicking it
* cause a redirect to just the id?
*
* trackLastUrl {boolean}
* When this tab is active, should the current path be tracked
* and persisted to session storage, then used as the tabs href attribute when the user navigates
* away from the tab?
*
* activeIndicatorColor {string}
* css color string that will be used to style the active
* indicator applied to tabs which are currently active.
*/
/**
* @param {TabSpec[]} tabSpecs
* @return {chrome}
*/
chrome.setTabs = function (tabSpecs) {
internals.tabs.set(tabSpecs);
return chrome;
};
/**
* @param {Object} defaults - defaults used for each tab
* @return {chrome}
*/
chrome.setTabDefaults = function (defaults) {
internals.tabs.setDefaults(defaults);
return chrome;
};
/**
* @return {Tab[]}
*/
chrome.getTabs = function () {
return internals.tabs.get();
};
/**
* @return {Tab}
*/
chrome.getActiveTab = function () {
return internals.tabs.getActive();
};
/**
* @param {any} def - the default value if there isn't any active tab
* @return {any}
*/
chrome.getActiveTabTitle = activeGetter('title');
// create a getter for properties of the active tab
function activeGetter(prop) {
return function (def) {
let active = chrome.getActiveTab();
return !active ? def : active[prop];
};
}
};

View file

@ -1,71 +1,45 @@
<div class="content" chrome-context >
<!-- TODO: These config dropdowns shouldn't be hard coded -->
<nav class="app-links-wrapper" ng-show="chrome.getVisible()">
<li
ng-if="!chrome.getBrand('logo') && !chrome.getBrand('smallLogo')"
ng-if="!chrome.getBrand('logo') && !chrome.getBrand('smallLogo')"
aria-label="{{ chrome.getAppTitle() }} Logo"
class="logo kibana"
></li>
></li>
<li
ng-if="chrome.getBrand('logo')"
ng-style="{ 'background': chrome.getBrand('logo') }"
aria-label="{{ chrome.getAppTitle() }} Logo"
class="logo hidden-sm"
></li>
></li>
<li
ng-if="chrome.getBrand('smallLogo')"
ng-style="{ 'background': chrome.getBrand('smallLogo') }"
aria-label="{{ chrome.getAppTitle() }} Logo"
class="logo-small visible-sm hidden-xs"
></li>
></li>
<app-switcher>
</app-switcher>
<div class="bottom-apps">
<div class="chrome-actions app-links" kbn-chrome-append-nav-controls></div>
</div>
</nav>
<div class="app-wrapper" ng-class="{ 'hidden-chrome': !chrome.getVisible() }">
<div class="app-wrapper-panel">
<kbn-notifications list="notifList"></kbn-notifications>
<nav
ng-style="::{ background: chrome.getNavBackground() }"
ng-class="{ show: chrome.getTabs().length > 0 }"
class="hide navbar navbar-inverse navbar-static-top">
<!-- Mobile navbar -->
<div class="navbar-header">
<button ng-click="showCollapsed = !showCollapsed" type="button" class="navbar-toggle">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="visible-xs">
<span ng-if="chrome.getBrand('title')" class="navbar-brand">{{ chrome.getBrand('title') }}</span>
<span ng-if="chrome.getActiveTabTitle()" class="navbar-brand">{{ chrome.getActiveTabTitle() }}</span>
</span>
</div>
<!-- /Mobile navbar -->
<!-- Full navbar -->
<div collapse="!showCollapsed" class="navbar-collapse" kbn-chrome-append-nav-controls>
<div ng-if="chrome.getBrand('title')" class="navbar-brand">{{ chrome.getBrand('title') }}</div>
<ul class="nav navbar-nav" role="navigation">
<li ng-repeat="tab in chrome.getTabs()" ng-class="{ active: tab.active }">
<a ng-href="{{ tab.href() }}" ng-style="{ 'border-bottom-color': tab.activeIndicatorColor }">
{{ tab.title }}
</a>
</li>
</ul>
</div>
<!-- /Full navbar -->
</nav>
<kbn-loading-indicator></kbn-loading-indicator>
<div class="application" ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()" ng-view></div>
<div
class="application"
ng-class="'tab-' + chrome.getFirstPathSegment() + ' ' + chrome.getApplicationClasses()"
ng-view
></div>
</div>
</div>
</div>

View file

@ -7,6 +7,7 @@ import metadata from 'ui/metadata';
import 'babel/polyfill';
import $ from 'jquery';
import 'ui/timefilter';
import 'ui/notify';
import 'ui/private';
import 'ui/promises';
import 'ui/directives/kbn_src';
@ -37,7 +38,6 @@ require('./api/xsrf')(chrome, internals);
require('./api/nav')(chrome, internals);
require('./api/angular')(chrome, internals);
require('./api/controls')(chrome, internals);
require('./api/tabs')(chrome, internals);
require('./api/template')(chrome, internals);
require('./api/theme')(chrome, internals);

View file

@ -33,8 +33,6 @@ export default function (chrome, internals) {
// listen for route changes, propogate to tabs
const onRouteChange = function () {
let { href } = window.location;
let persist = chrome.getVisible();
internals.tabs.consumeRouteUpdate(href, persist);
internals.trackPossibleSubUrl(href);
};

View file

@ -1,82 +0,0 @@
import notify from 'ui/notify';
import _ from 'lodash';
import { escapeRegExp as reEsc } from 'lodash';
import { parse, format } from 'url';
const urlJoin = (a, b) => {
if (!b) return a;
return `${a}${ a.endsWith('/') ? '' : '/' }${b}`;
};
export default class Tab {
constructor(spec = {}) {
this.id = spec.id || '';
this.title = spec.title || '';
this.resetWhenActive = !!spec.resetWhenActive;
this.activeIndicatorColor = spec.activeIndicatorColor || null;
if (_.isFunction(this.activeIndicatorColor)) {
// convert to a getter
Object.defineProperty(this, 'activeIndicatorColor', {
get: this.activeIndicatorColor
});
}
this.active = false;
this.baseUrl = spec.baseUrl || '/';
this.rootUrl = urlJoin(this.baseUrl, this.id);
this.rootRegExp = new RegExp(`^${reEsc(this.rootUrl)}(/|$|\\?|#)`);
this.lastUrlStoreKey = `lastUrl:${this.id}`;
this.lastUrlStore = spec.lastUrlStore;
this.lastUrl = null;
if (this.lastUrlStore) {
this.lastUrl = this.lastUrlStore.getItem(this.lastUrlStoreKey);
if (this.lastUrl && !this.lastUrl.startsWith(this.rootUrl)) {
notify.log(`Found invalid lastUrl for tab with root url ${this.rootUrl}: "${this.lastUrl}"`);
this.lastUrl = null;
this.lastUrlStore.removeItem(this.lastUrlStoreKey);
}
}
}
href() {
if (this.active) {
return this.resetWhenActive ? this.rootUrl : null;
}
return this.lastUrl || this.rootUrl;
}
updateLastUrlGlobalState(globalState) {
let lastPath = this.getLastPath();
let { pathname, query, hash } = parse(lastPath, true);
query = query || {};
if (!globalState) delete query._g;
else query._g = globalState;
this.setLastUrl(`${this.rootUrl}${format({ pathname, query, hash })}`);
}
getLastPath() {
let { id, rootUrl } = this;
let lastUrl = this.getLastUrl();
if (!lastUrl.startsWith(rootUrl)) {
notify.log(`Tab "${id}" has invalid root "${rootUrl}" for last url "${lastUrl}"`);
lastUrl = rootUrl;
}
return lastUrl.slice(rootUrl.length);
}
setLastUrl(url) {
this.lastUrl = url;
if (this.lastUrlStore) this.lastUrlStore.setItem(this.lastUrlStoreKey, this.lastUrl);
}
getLastUrl() {
return this.lastUrl || this.rootUrl;
}
}

View file

@ -1,53 +0,0 @@
import _ from 'lodash';
import Tab from 'ui/chrome/tab';
import { startsWith, get, set, omit, wrap, pick } from 'lodash';
import { parse } from 'url';
function TabCollection(opts = {}) {
let tabs = [];
let specs = null;
let defaults = opts.defaults || {};
let activeTab = null;
this.set = function (_specs) {
specs = _.cloneDeep([].concat(_specs || []));
this._rebuildTabs();
};
this.setDefaults = function () {
defaults = _.defaults({}, arguments[0], defaults);
this._rebuildTabs();
};
this.get = function () {
return [].concat(tabs || []);
};
this._rebuildTabs = function () {
_.invoke(this.get(), 'destroy');
tabs = _.map(specs, function (spec) {
return new Tab(_.defaults({}, spec, defaults));
});
};
this.getActive = function () {
return activeTab;
};
this.consumeRouteUpdate = function (href, persist) {
tabs.forEach(function (tab) {
tab.active = tab.rootRegExp.test(href);
if (tab.active) {
activeTab = tab;
activeTab.setLastUrl(href);
}
});
if (!persist || !activeTab) return;
let globalState = get(parse(activeTab.getLastPath(), true), 'query._g');
tabs.forEach(tab => tab.updateLastUrlGlobalState(globalState));
};
}
module.exports = TabCollection;

View file

@ -61,25 +61,12 @@ describe('docTitle Service', function () {
});
describe('#change', function () {
let getActiveTabStub;
beforeEach(function () {
getActiveTabStub = sinon.stub(require('ui/chrome'), 'getActiveTab');
});
it('writes the first param to as the first part of the doc name', function () {
expect(document.title).to.be(MAIN_TITLE);
docTitle.change('some secondary title');
expect(document.title).to.be('some secondary title - ' + MAIN_TITLE);
});
it('includes the title of the active tab if available', function () {
expect(document.title).to.be(MAIN_TITLE);
getActiveTabStub.returns({ title: 'fancy pants' });
docTitle.change('some secondary title');
expect(document.title).to.be('some secondary title - fancy pants - ' + MAIN_TITLE);
});
it('will write just the first param if the second param is true', function () {
expect(document.title).to.be(MAIN_TITLE);
docTitle.change('entire name', true);

View file

@ -8,7 +8,6 @@ uiModules.get('kibana')
$rootScope.$on('$routeChangeStart', docTitle.reset);
$rootScope.$on('$routeChangeError', docTitle.update);
$rootScope.$on('$routeChangeSuccess', docTitle.update);
$rootScope.$watch(_.bindKey(chrome, 'getActiveTabTitle'), docTitle.update);
})
.service('docTitle', function ($rootScope) {
let baseTitle = document.title;
@ -20,9 +19,6 @@ uiModules.get('kibana')
lastChange = lastChange || [];
let parts = [lastChange[0]];
let activeTabTitle = chrome.getActiveTabTitle();
if (activeTabTitle) parts.push(activeTabTitle);
if (!lastChange[1]) parts.push(baseTitle);