Skip to content

Commit

Permalink
Merge pull request elastic#7601 from ycombinator/app-switcher/nav-lin…
Browse files Browse the repository at this point in the history
…k-enhancements

[app switcher] nav link enhancements

Former-commit-id: 0169492
  • Loading branch information
ycombinator authored Jul 5, 2016
2 parents 14530f5 + e603442 commit 92253c0
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/core_plugins/kibana/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,31 @@ module.exports = function (kibana) {

links: [
{
id: 'kibana:discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
description: 'interactively explore your data',
icon: 'plugins/kibana/assets/discover.svg',
},
{
id: 'kibana:visualize',
title: 'Visualize',
order: -1002,
url: '/app/kibana#/visualize',
description: 'design data visualizations',
icon: 'plugins/kibana/assets/visualize.svg',
},
{
id: 'kibana:dashboard',
title: 'Dashboard',
order: -1001,
url: '/app/kibana#/dashboard',
description: 'compose visualizations for much win',
icon: 'plugins/kibana/assets/dashboard.svg',
},
{
id: 'kibana:management',
title: 'Management',
order: 1000,
url: '/app/kibana#/management',
Expand Down
159 changes: 159 additions & 0 deletions src/ui/__tests__/ui_nav_link.js
Original file line number Diff line number Diff line change
@@ -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));
});
});
});
29 changes: 29 additions & 0 deletions src/ui/public/chrome/api/__tests__/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
8 changes: 8 additions & 0 deletions src/ui/public/chrome/api/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || '';
};
Expand Down
13 changes: 10 additions & 3 deletions src/ui/public/chrome/directives/app_switcher/app_switcher.html
Original file line number Diff line number Diff line change
@@ -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>
Expand Down
19 changes: 17 additions & 2 deletions src/ui/public/chrome/directives/app_switcher/app_switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/ui/ui_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion src/ui/ui_nav_link.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
}
}

0 comments on commit 92253c0

Please sign in to comment.