Merge pull request #7601 from ycombinator/app-switcher/nav-link-enhancements

[app switcher] nav link enhancements
This commit is contained in:
Shaunak Kashyap 2016-07-05 15:39:05 -07:00 committed by GitHub
commit 016949216b
9 changed files with 241 additions and 7 deletions

View file

@ -40,6 +40,7 @@ module.exports = function (kibana) {
links: [
{
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
@ -47,6 +48,7 @@ module.exports = function (kibana) {
icon: 'plugins/kibana/assets/discover.svg',
},
{
id: 'kibana:visualize',
title: 'Visualize',
order: -1002,
url: '/app/kibana#/visualize',
@ -54,6 +56,7 @@ module.exports = function (kibana) {
icon: 'plugins/kibana/assets/visualize.svg',
},
{
id: 'kibana:dashboard',
title: 'Dashboard',
order: -1001,
url: '/app/kibana#/dashboard',
@ -61,6 +64,7 @@ module.exports = function (kibana) {
icon: 'plugins/kibana/assets/dashboard.svg',
},
{
id: 'kibana:management',
title: 'Management',
order: 1000,
url: '/app/kibana#/management',

View file

@ -0,0 +1,159 @@
import expect from 'expect.js';
import UiNavLink from '../ui_nav_link';
describe('UiNavLink', () => {
describe('constructor', () => {
it ('initializes the object properties as expected', () => {
const uiExports = {
urlBasePath: 'http://localhost:5601/rnd'
};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
hidden: true,
disabled: true
};
const link = new UiNavLink(uiExports, spec);
expect(link.id).to.be(spec.id);
expect(link.title).to.be(spec.title);
expect(link.order).to.be(spec.order);
expect(link.url).to.be(`${uiExports.urlBasePath}${spec.url}`);
expect(link.description).to.be(spec.description);
expect(link.icon).to.be(spec.icon);
expect(link.hidden).to.be(spec.hidden);
expect(link.disabled).to.be(spec.disabled);
});
it ('initializes the url property without a base path when one is not specified in the spec', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
expect(link.url).to.be(spec.url);
});
it ('initializes the order property to 0 when order is not specified in the spec', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
expect(link.order).to.be(0);
});
it ('initializes the linkToLastSubUrl property to false when false is specified in the spec', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
linkToLastSubUrl: false
};
const link = new UiNavLink(uiExports, spec);
expect(link.linkToLastSubUrl).to.be(false);
});
it ('initializes the linkToLastSubUrl property to true by default', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
expect(link.linkToLastSubUrl).to.be(true);
});
it ('initializes the hidden property to false by default', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
expect(link.hidden).to.be(false);
});
it ('initializes the disabled property to false by default', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
expect(link.disabled).to.be(false);
});
it ('initializes the tooltip property to an empty string by default', () => {
const uiExports = {};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
expect(link.tooltip).to.be('');
});
});
describe('#toJSON', () => {
it ('returns the expected properties', () => {
const uiExports = {
urlBasePath: 'http://localhost:5601/rnd'
};
const spec = {
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
};
const link = new UiNavLink(uiExports, spec);
const json = link.toJSON();
['id', 'title', 'url', 'order', 'description', 'icon', 'linkToLastSubUrl', 'hidden', 'disabled', 'tooltip']
.forEach(expectedProperty => expect(json).to.have.property(expectedProperty));
});
});
});

View file

@ -45,6 +45,35 @@ describe('chrome nav apis', function () {
});
});
describe('#getNavLinkById', () => {
it ('retrieves the correct nav link, given its ID', () => {
const appUrlStore = new StubBrowserStorage();
const nav = [
{ id: 'kibana:discover', title: 'Discover' }
];
const { chrome, internals } = init({ appUrlStore, nav });
const navLink = chrome.getNavLinkById('kibana:discover');
expect(navLink).to.eql(nav[0]);
});
it ('throws an error if the nav link with the given ID is not found', () => {
const appUrlStore = new StubBrowserStorage();
const nav = [
{ id: 'kibana:discover', title: 'Discover' }
];
const { chrome, internals } = init({ appUrlStore, nav });
let errorThrown = false;
try {
const navLink = chrome.getNavLinkById('nonexistent');
} catch (e) {
errorThrown = true;
}
expect(errorThrown).to.be(true);
});
});
describe('internals.trackPossibleSubUrl()', function () {
it('injects the globalState of the current url to all links for the same app', function () {
const appUrlStore = new StubBrowserStorage();

View file

@ -6,6 +6,14 @@ export default function (chrome, internals) {
return internals.nav;
};
chrome.getNavLinkById = (id) => {
const navLink = internals.nav.find(link => link.id === id);
if (!navLink) {
throw new Error(`Nav link for id = ${id} not found`);
}
return navLink;
};
chrome.getBasePath = function () {
return internals.basePath || '';
};

View file

@ -1,13 +1,20 @@
<div class="app-links">
<div
class="app-link"
ng-repeat="link in switcher.getNavLinks()"
ng-class="{ active: link.active }">
ng-repeat="link in switcher.shownNavLinks"
ng-class="{ active: link.active, 'is-app-switcher-app-link-disabled': !switcher.isNavLinkEnabled(link) }"
tooltip="{{link.tooltip}}"
tooltip-placement="right"
tooltip-popup-delay="400"
tooltip-append-to-body="1"
>
<a
ng-click="switcher.ensureNavigation($event, link)"
ng-href="{{ link.active ? link.url : (link.lastSubUrl || link.url) }}"
data-test-subj="appLink">
data-test-subj="appLink"
ng-class="{ 'app-link__anchor': !switcher.isNavLinkEnabled(link) }"
>
<div ng-if="link.icon" class="app-icon"><img kbn-src="{{'/' + link.icon}}"></div>
<div ng-if="!link.icon" class="app-icon-missing">{{ link.title[0] }}</div>

View file

@ -5,6 +5,14 @@ import '../app_switcher/app_switcher.less';
import uiModules from 'ui/modules';
import appSwitcherTemplate from './app_switcher.html';
function isNavLinkEnabled(link) {
return !link.disabled;
}
function isNavLinkShown(link) {
return !link.hidden;
}
uiModules
.get('kibana')
.provider('appSwitcherEnsureNavigation', function () {
@ -17,7 +25,11 @@ uiModules
this.$get = ['Private', function (Private) {
const domLocation = Private(DomLocationProvider);
return function (event) {
return function (event, link) {
if (!isNavLinkEnabled(link)) {
event.preventDefault();
}
if (!forceNavigation || event.isDefaultPrevented() || event.altKey || event.metaKey || event.ctrlKey) {
return;
}
@ -52,7 +64,10 @@ uiModules
throw new TypeError('appSwitcher directive requires the "chrome" config-object');
}
this.getNavLinks = bindKey($scope.chrome, 'getNavLinks');
this.isNavLinkEnabled = isNavLinkEnabled;
const allNavLinks = $scope.chrome.getNavLinks();
this.shownNavLinks = allNavLinks.filter(isNavLinkShown);
// links don't cause full-navigation events in certain scenarios
// so we force them when needed

View file

@ -79,13 +79,20 @@ body { overflow-x: hidden; }
height: @app-icon-height;
line-height: @app-line-height;
> a {
display: block;
height: 100%;
color: #ebf7fa;
}
&.is-app-switcher-app-link-disabled {
opacity: 0.5;
.app-link__anchor {
cursor: default;
}
}
.app-icon {
float: left;
font-weight: bold;

View file

@ -22,6 +22,7 @@ class UiApp {
if (!this.hidden) {
// any non-hidden app has a url, so it gets a "navLink"
this.navLink = this.uiExports.navLinks.new({
id: this.id,
title: this.title,
description: this.description,
icon: this.icon,

View file

@ -3,15 +3,19 @@ import { join } from 'path';
export default class UiNavLink {
constructor(uiExports, spec) {
this.id = spec.id;
this.title = spec.title;
this.order = spec.order || 0;
this.url = `${uiExports.urlBasePath || ''}${spec.url}`;
this.description = spec.description;
this.icon = spec.icon;
this.linkToLastSubUrl = spec.linkToLastSubUrl === false ? false : true;
this.hidden = spec.hidden || false;
this.disabled = spec.disabled || false;
this.tooltip = spec.tooltip || '';
}
toJSON() {
return pick(this, ['title', 'url', 'order', 'description', 'icon', 'linkToLastSubUrl']);
return pick(this, ['id', 'title', 'url', 'order', 'description', 'icon', 'linkToLastSubUrl', 'hidden', 'disabled', 'tooltip']);
}
}