Merge pull request #7601 from ycombinator/app-switcher/nav-link-enhancements
[app switcher] nav link enhancements
This commit is contained in:
commit
016949216b
|
@ -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',
|
||||
|
|
159
src/ui/__tests__/ui_nav_link.js
Normal file
159
src/ui/__tests__/ui_nav_link.js
Normal 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));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
|
|
|
@ -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 || '';
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue