diff --git a/src/plugins/kibana/index.js b/src/plugins/kibana/index.js
index b1ea675fd1057..9b1d926aa9664 100644
--- a/src/plugins/kibana/index.js
+++ b/src/plugins/kibana/index.js
@@ -2,7 +2,7 @@ import ingest from './server/routes/api/ingest';
module.exports = function (kibana) {
return new kibana.Plugin({
-
+ id: 'kibana',
config: function (Joi) {
return Joi.object({
enabled: Joi.boolean().default(true),
@@ -13,7 +13,9 @@ module.exports = function (kibana) {
uiExports: {
app: {
+ id: 'kibana',
title: 'Kibana',
+ listed: false,
description: 'the kibana you know and love',
//icon: 'plugins/kibana/settings/sections/about/barcode.svg',
main: 'plugins/kibana/kibana',
@@ -32,8 +34,39 @@ module.exports = function (kibana) {
return {
kbnDefaultAppId: config.get('kibana.defaultAppId')
};
+ },
+ },
+
+ links: [
+ {
+ title: 'Discover',
+ order: -1003,
+ url: '/app/kibana#/discover',
+ description: 'interactively explore your data',
+ icon: 'plugins/kibana/assets/discover.svg',
+ },
+ {
+ title: 'Visualize',
+ order: -1002,
+ url: '/app/kibana#/visualize',
+ description: 'design data visualizations',
+ icon: 'plugins/kibana/assets/visualize.svg',
+ },
+ {
+ title: 'Dashboard',
+ order: -1001,
+ url: '/app/kibana#/dashboard',
+ description: 'compose visualizations for much win',
+ icon: 'plugins/kibana/assets/dashboard.svg',
+ },
+ {
+ title: 'Settings',
+ order: 1000,
+ url: '/app/kibana#/settings',
+ description: 'define index patterns, change config, and more',
+ icon: 'plugins/kibana/assets/settings.svg',
}
- }
+ ]
},
init: function (server, options) {
diff --git a/src/plugins/kibana/public/assets/dashboard.svg b/src/plugins/kibana/public/assets/dashboard.svg
new file mode 100755
index 0000000000000..083918d1c51b0
--- /dev/null
+++ b/src/plugins/kibana/public/assets/dashboard.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/plugins/kibana/public/assets/discover.svg b/src/plugins/kibana/public/assets/discover.svg
new file mode 100755
index 0000000000000..7b923b7c8aad2
--- /dev/null
+++ b/src/plugins/kibana/public/assets/discover.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/plugins/kibana/public/assets/logout.svg b/src/plugins/kibana/public/assets/logout.svg
new file mode 100755
index 0000000000000..123ceceaef10e
--- /dev/null
+++ b/src/plugins/kibana/public/assets/logout.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/plugins/kibana/public/assets/search.svg b/src/plugins/kibana/public/assets/search.svg
new file mode 100755
index 0000000000000..1e4ffb6c1b4fc
--- /dev/null
+++ b/src/plugins/kibana/public/assets/search.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/plugins/kibana/public/assets/settings.svg b/src/plugins/kibana/public/assets/settings.svg
new file mode 100755
index 0000000000000..7bbbce4ac0874
--- /dev/null
+++ b/src/plugins/kibana/public/assets/settings.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/plugins/kibana/public/assets/visualize.svg b/src/plugins/kibana/public/assets/visualize.svg
new file mode 100755
index 0000000000000..a1cde5a166481
--- /dev/null
+++ b/src/plugins/kibana/public/assets/visualize.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/plugins/kibana/public/kibana.js b/src/plugins/kibana/public/kibana.js
index 6c0182605ae06..b7fe5cc84875a 100644
--- a/src/plugins/kibana/public/kibana.js
+++ b/src/plugins/kibana/public/kibana.js
@@ -28,13 +28,13 @@ routes
chrome
.setBrand({
- 'logo': 'url(' + kibanaLogoUrl + ') center / 160px 70px no-repeat #e8488b',
- 'smallLogo': 'url(' + kibanaLogoUrl + ') center / 160px 70px no-repeat #e8488b'
+ 'logo': 'url(' + kibanaLogoUrl + ') 6px 10px / 140px 50px no-repeat #e8488b',
+ 'smallLogo': 'url(' + kibanaLogoUrl + ') 6px 10px / 140px 50px no-repeat #e8488b'
})
.setNavBackground('#222222')
.setTabDefaults({
resetWhenActive: true,
- lastUrlStore: window.sessionStore,
+ lastUrlStore: window.sessionStorage,
activeIndicatorColor: '#656a76'
})
.setTabs([
diff --git a/src/plugins/statusPage/public/statusPage.js b/src/plugins/statusPage/public/statusPage.js
index b7ce8705fc5c6..32ba83efbd756 100644
--- a/src/plugins/statusPage/public/statusPage.js
+++ b/src/plugins/statusPage/public/statusPage.js
@@ -54,3 +54,8 @@ const chrome = require('ui/chrome')
ui.refresh();
});
+
+require('ui/modules').get('kibana')
+.config(function (appSwitcherEnsureNavigationProvider) {
+ appSwitcherEnsureNavigationProvider.forceNavigation(true);
+});
diff --git a/src/ui/index.js b/src/ui/index.js
index 2c3981c5fadf4..aae26b9a86a69 100644
--- a/src/ui/index.js
+++ b/src/ui/index.js
@@ -71,7 +71,7 @@ module.exports = async (kbnServer, server, config) => {
server.decorate('reply', 'renderApp', function (app) {
const payload = {
app: app,
- nav: uiExports.apps,
+ nav: uiExports.navLinks.inOrder,
version: kbnServer.version,
buildNum: config.get('pkg.buildNum'),
buildSha: config.get('pkg.buildSha'),
diff --git a/src/ui/public/chrome/Tab.js b/src/ui/public/chrome/Tab.js
index 1a6884047f5ae..7cc9a108ab9ae 100644
--- a/src/ui/public/chrome/Tab.js
+++ b/src/ui/public/chrome/Tab.js
@@ -1,3 +1,4 @@
+import notify from 'ui/notify';
import _ from 'lodash';
import { escapeRegExp as reEsc } from 'lodash';
import { parse, format } from 'url';
@@ -28,7 +29,16 @@ export default class Tab {
this.lastUrlStoreKey = `lastUrl:${this.id}`;
this.lastUrlStore = spec.lastUrlStore;
- this.lastUrl = this.lastUrlStore ? this.lastUrlStore.getItem(this.lastUrlStoreKey) : null;
+
+ 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() {
@@ -54,7 +64,8 @@ export default class Tab {
let lastUrl = this.getLastUrl();
if (!lastUrl.startsWith(rootUrl)) {
- throw new Error(`Tab "${id}" has invalid root "${rootUrl}" for last url "${lastUrl}"`);
+ notify.log(`Tab "${id}" has invalid root "${rootUrl}" for last url "${lastUrl}"`);
+ lastUrl = rootUrl;
}
return lastUrl.slice(rootUrl.length);
diff --git a/src/ui/public/chrome/__tests__/Tab.js b/src/ui/public/chrome/__tests__/Tab.js
index 6f4b05adc402c..2d13e2da5edcf 100644
--- a/src/ui/public/chrome/__tests__/Tab.js
+++ b/src/ui/public/chrome/__tests__/Tab.js
@@ -1,3 +1,4 @@
+import sinon from 'auto-release-sinon';
import Tab from '../Tab';
import expect from 'expect.js';
import TabFakeStore from './_TabFakeStore';
@@ -89,13 +90,24 @@ describe('Chrome Tab', function () {
it('discovers the lastUrl', function () {
const lastUrlStore = new TabFakeStore();
const tab = new Tab({ id: 'foo', lastUrlStore });
- expect(tab.lastUrl).to.not.equal('bar');
+ expect(tab.lastUrl).to.not.equal('/foo/bar');
- tab.setLastUrl('bar');
- expect(tab.lastUrl).to.equal('bar');
+ tab.setLastUrl('/foo/bar');
+ expect(tab.lastUrl).to.equal('/foo/bar');
const tab2 = new Tab({ id: 'foo', lastUrlStore });
- expect(tab2.lastUrl).to.equal('bar');
+ expect(tab2.lastUrl).to.equal('/foo/bar');
+ });
+
+ it('logs a warning about last urls that do not match the rootUrl', function () {
+ const lastUrlStore = new TabFakeStore();
+ 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);
});
});
@@ -167,14 +179,14 @@ describe('Chrome Tab', function () {
expect(tab.getLastPath()).to.equal('/index');
});
- it('throws an error if the lastUrl does not extend the root url', function () {
- expect(function () {
- const baseUrl = 'http://local:5601/app/visualize#';
- const tab = new Tab({ baseUrl });
+ 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();
- }).to.throwError(/invalid.*root/);
+ tab.setLastUrl('http://local:5601/');
+ tab.getLastPath();
+ sinon.assert.calledOnce(console.log);// eslint-disable-line no-console
});
});
diff --git a/src/ui/public/chrome/__tests__/_TabFakeStore.js b/src/ui/public/chrome/__tests__/_TabFakeStore.js
index 1b8ab81602259..a678440d14b78 100644
--- a/src/ui/public/chrome/__tests__/_TabFakeStore.js
+++ b/src/ui/public/chrome/__tests__/_TabFakeStore.js
@@ -4,6 +4,7 @@ export default class TabFakeStore {
constructor() { this[store] = new Map(); }
getItem(k) { return this[store].get(k); }
setItem(k, v) { return this[store].set(k, 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/api/__tests__/apps.js b/src/ui/public/chrome/api/__tests__/apps.js
index 059cc2a40d9ab..498dec79c5981 100644
--- a/src/ui/public/chrome/api/__tests__/apps.js
+++ b/src/ui/public/chrome/api/__tests__/apps.js
@@ -86,11 +86,11 @@ describe('Chrome API :: apps', function () {
describe('#getAppUrl()', function () {
it('returns the resolved url of the current app', function () {
const chrome = {};
- const app = { url: '/foo' };
+ const app = { navLink: { url: '/foo' } };
setup(chrome, { app });
const a = document.createElement('a');
- a.setAttribute('href', app.url);
+ a.setAttribute('href', app.navLink.url);
expect(chrome.getAppUrl()).to.equal(a.href);
});
diff --git a/src/ui/public/chrome/api/apps.js b/src/ui/public/chrome/api/apps.js
index ee6b7b1b86aff..c94dd537c28c7 100644
--- a/src/ui/public/chrome/api/apps.js
+++ b/src/ui/public/chrome/api/apps.js
@@ -3,8 +3,8 @@ import { resolve } from 'url';
module.exports = function (chrome, internals) {
- if (internals.app) {
- internals.app.url = resolve(window.location.href, internals.app.url);
+ if (get(internals, 'app.navLink.url')) {
+ internals.app.navLink.url = resolve(window.location.href, internals.app.navLink.url);
}
internals.appUrlStore = internals.appUrlStore || window.sessionStorage;
@@ -35,7 +35,7 @@ module.exports = function (chrome, internals) {
};
chrome.getAppUrl = function () {
- return get(internals, ['app', 'url']);
+ return get(internals, ['app', 'navLink', 'url']);
};
chrome.getInjected = function (name, def) {
diff --git a/src/ui/public/chrome/api/nav.js b/src/ui/public/chrome/api/nav.js
index 297df1543be3a..0e100ba1119c0 100644
--- a/src/ui/public/chrome/api/nav.js
+++ b/src/ui/public/chrome/api/nav.js
@@ -1,15 +1,11 @@
import { parse, format } from 'url';
-import { startsWith, isString } from 'lodash';
+import { startsWith, isString, find } from 'lodash';
export default function (chrome, internals) {
chrome.getNavLinks = function () {
return internals.nav;
};
- chrome.getLastSubUrlFor = function (url) {
- return internals.appUrlStore.getItem(`lastSubUrl:${url}`);
- };
-
chrome.getBasePath = function () {
return internals.basePath || '';
};
@@ -34,26 +30,46 @@ export default function (chrome, internals) {
});
};
+ function lastSubUrlKey(link) {
+ return `lastSubUrl:${link.url}`;
+ }
+
+ function setLastUrl(link, url) {
+ link.lastSubUrl = url;
+ internals.appUrlStore.setItem(lastSubUrlKey(link), url);
+ }
+
+ function refreshLastUrl(link) {
+ link.lastSubUrl = internals.appUrlStore.getItem(lastSubUrlKey(link));
+ }
+
internals.trackPossibleSubUrl = function (url) {
for (const link of internals.nav) {
- if (startsWith(url, link.url)) {
- link.lastSubUrl = url;
- internals.appUrlStore.setItem(`lastSubUrl:${link.url}`, 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;
+ }
+
+ refreshLastUrl(link);
}
};
internals.nav.forEach(link => {
// convert all link urls to absolute urls
-
var a = document.createElement('a');
a.setAttribute('href', link.url);
link.url = a.href;
- link.lastSubUrl = chrome.getLastSubUrlFor(link.url);
-
- if (link.url === chrome.getAppUrl()) {
- link.active = true;
- }
});
+ // simulate a possible change in url to initialize the
+ // link.active and link.lastUrl properties
+ internals.trackPossibleSubUrl(document.location.href);
};
diff --git a/src/ui/public/chrome/chrome.html b/src/ui/public/chrome/chrome.html
index f3c9ff4e06682..a1a2364f80be1 100644
--- a/src/ui/public/chrome/chrome.html
+++ b/src/ui/public/chrome/chrome.html
@@ -43,51 +43,53 @@
-
-
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.html b/src/ui/public/chrome/directives/app_switcher/app_switcher.html
index 7613cb03142b4..382e35b8a4023 100644
--- a/src/ui/public/chrome/directives/app_switcher/app_switcher.html
+++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.html
@@ -1,7 +1,7 @@
-
- {{ link.title[0] }}
+
+ {{ link.title[0] }}
{{ link.title }}
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.js b/src/ui/public/chrome/directives/app_switcher/app_switcher.js
index 77c8a4834adce..d8f6d5cd6ccb1 100644
--- a/src/ui/public/chrome/directives/app_switcher/app_switcher.js
+++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.js
@@ -1,4 +1,3 @@
-
import DomLocationProvider from 'ui/domLocation';
import { parse } from 'url';
import { bindKey } from 'lodash';
@@ -8,14 +7,45 @@ import appSwitcherTemplate from './app_switcher.html';
uiModules
.get('kibana')
+.provider('appSwitcherEnsureNavigation', function () {
+ let forceNavigation = false;
+
+ this.forceNavigation = function (val) {
+ forceNavigation = !!val;
+ };
+
+ this.$get = ['Private', function (Private) {
+ const domLocation = Private(DomLocationProvider);
+
+ return function (event) {
+ if (!forceNavigation || event.isDefaultPrevented() || event.altKey || event.metaKey || event.ctrlKey) {
+ return;
+ }
+
+ const toParsed = parse(event.delegateTarget.href);
+ const fromParsed = parse(domLocation.href);
+ const sameProto = toParsed.protocol === fromParsed.protocol;
+ const sameHost = toParsed.host === fromParsed.host;
+ const samePath = toParsed.path === fromParsed.path;
+
+ if (sameProto && sameHost && samePath) {
+ toParsed.hash && domLocation.reload();
+
+ // event.preventDefault() keeps the browser from seeing the new url as an update
+ // and even setting window.location does not mimic that behavior, so instead
+ // we use stopPropagation() to prevent angular from seeing the click and
+ // starting a digest cycle/attempting to handle it in the router.
+ event.stopPropagation();
+ }
+ };
+ }];
+})
.directive('appSwitcher', function () {
return {
restrict: 'E',
template: appSwitcherTemplate,
controllerAs: 'switcher',
- controller: function ($scope, Private) {
- var domLocation = Private(DomLocationProvider);
-
+ controller($scope, appSwitcherEnsureNavigation) {
// since we render this in an isolate scope we can't "require: ^chrome", but
// rather than remove all helpfull checks we can just check here.
if (!$scope.chrome || !$scope.chrome.getNavLinks) {
@@ -26,28 +56,7 @@ uiModules
// links don't cause full-navigation events in certain scenarios
// so we force them when needed
- this.ensureNavigation = function (event, app) {
- if (event.isDefaultPrevented() || event.altKey || event.metaKey || event.ctrlKey) {
- return;
- }
-
- var toParsed = parse(event.delegateTarget.href);
- var fromParsed = parse(domLocation.href);
- var sameProto = toParsed.protocol === fromParsed.protocol;
- var sameHost = toParsed.host === fromParsed.host;
- var samePath = toParsed.path === fromParsed.path;
-
- if (sameProto && sameHost && samePath) {
- toParsed.hash && domLocation.reload();
-
- // event.preventDefault() keeps the browser from seeing the new url as an update
- // and even setting window.location does not mimic that behavior, so instead
- // we use stopPropagation() to prevent angular from seeing the click and
- // starting a digest cycle/attempting to handle it in the router.
- event.stopPropagation();
- }
- };
-
+ this.ensureNavigation = appSwitcherEnsureNavigation;
}
};
});
diff --git a/src/ui/public/chrome/directives/app_switcher/app_switcher.less b/src/ui/public/chrome/directives/app_switcher/app_switcher.less
index f6ad38223eda1..2d2ac5564f04c 100644
--- a/src/ui/public/chrome/directives/app_switcher/app_switcher.less
+++ b/src/ui/public/chrome/directives/app_switcher/app_switcher.less
@@ -2,11 +2,12 @@
@import (reference) "~ui/styles/variables";
// as - App Switcher
-@as-open-width: 170px;
+@as-open-width: 160px;
@as-closed-width: 53px;
@app-icon-height: 30px;
-@transition-time: .65s;
-@transition-delay: .2s;
+@transition-time: .35s;
+@transition-delay: .1s;
+@app-links-wrapper-background: #3caed2;
body { overflow-x: hidden; }
@@ -17,7 +18,7 @@ body { overflow-x: hidden; }
top: 0;
bottom: 0;
z-index: 0;
- background-color: #3caed2;
+ background-color: @app-links-wrapper-background;
overflow: hidden;
transition: width @transition-time;
transition-delay: @transition-delay;
@@ -36,7 +37,11 @@ body { overflow-x: hidden; }
width: @as-open-width;
list-style-type: none;
&.kibana {
- background: url("~ui/images/kibana.svg") center / 160px 70px no-repeat #e8488b;
+ background-image: url("~ui/images/kibana.svg");
+ background-position: 6px 10px;
+ background-size: 140px 50px;
+ background-repeat: no-repeat;
+ background-color: #e8488b;
}
}
.bottom-apps {
@@ -57,6 +62,11 @@ body { overflow-x: hidden; }
margin: 0 auto;
background-color: #fff;
+ &-panel {
+ .flex-parent(@shrink: 0);
+ box-shadow: -4px 0px 3px rgba(0,0,0,0.2);
+ }
+
.navbar-right {
margin-right: 0;
}
@@ -67,22 +77,45 @@ body { overflow-x: hidden; }
.app-link {
width: @as-open-width;
height: @app-icon-height;
+ line-height: 24px;
+
> a {
display: block;
height: 100%;
color: #fff;
}
+
+ &:hover {
+ background-color: lighten(@app-links-wrapper-background, 7.5%);
+ }
+
.app-icon {
float: left;
+ filter: invert(100%);
+ font-weight: bold;
text-align: center;
font-size: 1.7em;
display: inline-block;
height: @app-icon-height;
width: @as-closed-width;
- background-position: center;
- background-size: contain;
> i {
}
+
+ > img {
+ height: 20px;
+ }
+ }
+
+ .app-icon-missing {
+ float: left;
+ text-align: center;
+ font-size: 1.7em;
+ display: inline-block;
+ height: @app-icon-height;
+ width: @as-closed-width;
+ background-position: center;
+ background-size: contain;
+ background-repeat: no-repeat;
}
.app-title {
@@ -90,16 +123,20 @@ body { overflow-x: hidden; }
display: inline-block;
float: right;
font-size: 0.9em;
- text-align: center;
+ text-align: left;
+ padding-left: 3px;
line-height: @app-icon-height;
}
&.active {
- background-color: #73c6e0;
+ background-color: lighten(@app-links-wrapper-background, 10%);
> a {
color: #333;
text-decoration: none;
}
+ img {
+ filter: invert(100%);
+ }
}
}
diff --git a/src/ui/public/chrome/directives/kbn_chrome.js b/src/ui/public/chrome/directives/kbn_chrome.js
index d6e9ddeed4a43..363a65a8e2768 100644
--- a/src/ui/public/chrome/directives/kbn_chrome.js
+++ b/src/ui/public/chrome/directives/kbn_chrome.js
@@ -35,8 +35,8 @@ export default function (chrome, internals) {
const onRouteChange = function () {
let { href } = window.location;
let persist = chrome.getVisible();
- internals.trackPossibleSubUrl(href);
internals.tabs.consumeRouteUpdate(href, persist);
+ internals.trackPossibleSubUrl(href);
};
$rootScope.$on('$routeChangeSuccess', onRouteChange);
diff --git a/src/ui/public/images/kibana.svg b/src/ui/public/images/kibana.svg
index dfb45b1544b7b..435adefaf46fc 100644
--- a/src/ui/public/images/kibana.svg
+++ b/src/ui/public/images/kibana.svg
@@ -1 +1,110 @@
-
\ No newline at end of file
+
+
diff --git a/src/ui/ui_app.js b/src/ui/ui_app.js
index 438908aa26e74..11327bdb84d63 100644
--- a/src/ui/ui_app.js
+++ b/src/ui/ui_app.js
@@ -1,5 +1,4 @@
-import _ from 'lodash';
-import { join } from 'path';
+import { chain, get, noop, once, pick } from 'lodash';
class UiApp {
constructor(uiExports, spec) {
@@ -8,6 +7,7 @@ class UiApp {
this.id = this.spec.id;
if (!this.id) {
+ console.log('-----------Spec Without ID:', this.spec);
throw new Error('Every app must specify it\'s id');
}
@@ -15,9 +15,24 @@ class UiApp {
this.title = this.spec.title;
this.description = this.spec.description;
this.icon = this.spec.icon;
- this.hidden = this.spec.hidden;
+ this.hidden = !!this.spec.hidden;
+ this.listed = this.spec.listed == null ? !this.hidden : this.spec.listed;
this.templateName = this.spec.templateName || 'ui_app';
- this.url = `${spec.urlBasePath || ''}${this.spec.url || `/app/${this.id}`}`;
+
+ if (!this.hidden) {
+ // any non-hidden app has a url, so it gets a "navLink"
+ this.navLink = this.uiExports.navLinks.new({
+ title: this.title,
+ description: this.description,
+ icon: this.icon,
+ url: this.spec.url || `/app/${this.id}`
+ });
+
+ if (!this.listed) {
+ // unlisted apps remove their navLinks from the uiExports collection though
+ this.uiExports.navLinks.delete(this.navLink);
+ }
+ }
if (this.spec.autoload) {
console.warn(
@@ -27,15 +42,15 @@ class UiApp {
}
// once this resolves, no reason to run it again
- this.getModules = _.once(this.getModules);
+ this.getModules = once(this.getModules);
// variables that are injected into the browser, must serialize to JSON
- this.getInjectedVars = this.spec.injectVars || _.noop;
+ this.getInjectedVars = this.spec.injectVars || noop;
}
getModules() {
- return _.chain([
- this.uiExports.find(_.get(this, 'spec.uses', [])),
+ return chain([
+ this.uiExports.find(get(this, 'spec.uses', [])),
this.uiExports.find(['chromeNavControls', 'sledgehammers']),
])
.flatten()
@@ -45,7 +60,7 @@ class UiApp {
}
toJSON() {
- return _.pick(this, ['id', 'title', 'description', 'icon', 'main', 'url']);
+ return pick(this, ['id', 'title', 'description', 'icon', 'main', 'navLink']);
}
}
diff --git a/src/ui/ui_app_collection.js b/src/ui/ui_app_collection.js
index a00d9646d557f..1a41a53b40e29 100644
--- a/src/ui/ui_app_collection.js
+++ b/src/ui/ui_app_collection.js
@@ -21,6 +21,7 @@ module.exports = class UiAppCollection extends Collection {
}
new(spec) {
+ console.log('--------NewAppSpec:', spec);
if (this.hidden && spec.hidden) {
return this.hidden.new(spec);
}
diff --git a/src/ui/ui_exports.js b/src/ui/ui_exports.js
index f7be016258bac..89e64b9f9dc2a 100644
--- a/src/ui/ui_exports.js
+++ b/src/ui/ui_exports.js
@@ -2,9 +2,11 @@ import _ from 'lodash';
import minimatch from 'minimatch';
import UiAppCollection from './ui_app_collection';
+import UiNavLinkCollection from './ui_nav_link_collection';
class UiExports {
constructor({ urlBasePath }) {
+ this.navLinks = new UiNavLinkCollection(this);
this.apps = new UiAppCollection(this);
this.aliases = {};
this.urlBasePath = urlBasePath;
@@ -48,15 +50,21 @@ class UiExports {
case 'app':
case 'apps':
return (plugin, specs) => {
+ const id = plugin.id;
for (let spec of [].concat(specs || [])) {
- let app = this.apps.new(_.defaults({}, spec, {
- id: plugin.id,
- urlBasePath: this.urlBasePath
- }));
+ const app = this.apps.new({ id, ...spec });
plugin.apps.add(app);
}
};
+ case 'link':
+ case 'links':
+ return (plugin, spec) => {
+ for (const spec of [].concat(spec || [])) {
+ this.navLinks.new(spec);
+ }
+ };
+
case 'visTypes':
case 'fieldFormats':
case 'spyModes':
diff --git a/src/ui/ui_nav_link.js b/src/ui/ui_nav_link.js
new file mode 100644
index 0000000000000..503a31073834b
--- /dev/null
+++ b/src/ui/ui_nav_link.js
@@ -0,0 +1,16 @@
+import { pick } from 'lodash';
+import { join } from 'path';
+
+export default class UiNavLink {
+ constructor(uiExports, spec) {
+ this.title = spec.title;
+ this.order = spec.order || 0;
+ this.url = `${uiExports.urlBasePath || ''}${spec.url}`;
+ this.description = spec.description;
+ this.icon = spec.icon;
+ }
+
+ toJSON() {
+ return pick(this, ['title', 'url', 'order', 'description', 'icon']);
+ }
+}
diff --git a/src/ui/ui_nav_link_collection.js b/src/ui/ui_nav_link_collection.js
new file mode 100644
index 0000000000000..c88bf897b242f
--- /dev/null
+++ b/src/ui/ui_nav_link_collection.js
@@ -0,0 +1,29 @@
+import { sortBy } from 'lodash';
+import UiNavLink from './ui_nav_link';
+import Collection from '../utils/Collection';
+
+const inOrderCache = Symbol('inOrder');
+
+export default class UiNavLinkCollection extends Collection {
+
+ constructor(uiExports, parent) {
+ super();
+ this.uiExports = uiExports;
+ }
+
+ new(spec) {
+ const link = new UiNavLink(this.uiExports, spec);
+ this[inOrderCache] = null;
+ this.add(link);
+ return link;
+ }
+
+ get inOrder() {
+ if (!this[inOrderCache]) {
+ this[inOrderCache] = sortBy([...this], 'order');
+ }
+
+ return this[inOrderCache];
+ }
+
+};
diff --git a/tasks/utils/files_to_commit.js b/tasks/utils/files_to_commit.js
index 61f55fcc02a19..a149c377aa611 100644
--- a/tasks/utils/files_to_commit.js
+++ b/tasks/utils/files_to_commit.js
@@ -5,6 +5,7 @@ import { includes } from 'lodash';
export default function filesToCommit(path) {
const simpleGit = new SimpleGit(path);
const gitDiff = promisify(simpleGit.diff, simpleGit);
+ const pathsToIgnore = ['webpackShims'];
return gitDiff(['--name-status', '--cached'])
.then(output => {
@@ -12,6 +13,9 @@ export default function filesToCommit(path) {
.split('\n')
.map(line => line.trim().split('\t'))
.filter(parts => parts.length === 2)
+ .filter(parts => {
+ return pathsToIgnore.reduce((prev, curr) => {(prev === false) ? prev : parts[1].indexOf(curr) === -1;}, true);
+ })
.map(parts => {
const status = parts.shift();
const name = parts.join('\t').trim();
diff --git a/test/support/pages/common.js b/test/support/pages/common.js
index c246031a45437..c68b1a4131b68 100644
--- a/test/support/pages/common.js
+++ b/test/support/pages/common.js
@@ -154,11 +154,11 @@ define(function (require) {
var self = this;
return self.remote.setFindTimeout(defaultTimeout)
- .findByCssSelector('.app-wrapper > .application')
+ .findByCssSelector('.app-wrapper .application')
.then(function () {
return self.runScript(function () {
var $ = window.$;
- var $scope = $('.app-wrapper > .application').scope();
+ var $scope = $('.app-wrapper .application').scope();
return $scope ? $scope.chrome.getApp() : {};
});
});