-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[app switcher] nav link enhancements #7601
Changes from 16 commits
8df249b
2712203
96588bd
b25456a
0b1acad
9161c0c
bb45a39
921533a
7e931bd
a8995e3
a376c03
f4390ba
30032c5
8dac9c3
2601fcf
42cc301
de1ea90
81072ad
03eebad
3a8948f
c8b4d2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,36 @@ describe('chrome nav apis', function () { | |
}); | ||
}); | ||
|
||
describe('#getNavLinkById', () => { | ||
it ('retrieves the correct nav link, given its ID', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be a test for the case where the id is not found. If possible, it'd be great if we could throw in that case, what do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added. |
||
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.not.be(undefined); | ||
expect(navLink.title).to.be('Discover'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these two expectations can be replaced with: expect(navLink).to.eql(nav[0]); This should do a deep comparison: http://chaijs.com/api/bdd/#method_eql There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah cool. Will give this a shot. Thanks! |
||
}); | ||
|
||
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(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,15 @@ export default function (chrome, internals) { | |
return internals.nav; | ||
}; | ||
|
||
chrome.getNavLinkById = (id) => { | ||
const navLink = internals.nav.find(link => link.id === id); | ||
if (navLink) { | ||
return navLink; | ||
} else { | ||
throw new Error(`Nav link for id = ${id} not found`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the spirit of Return early from functions, I recommend doing this assertion up front and then returning outside of the conditional: 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 || ''; | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,18 @@ | ||
import DomLocationProvider from 'ui/dom_location'; | ||
import { parse } from 'url'; | ||
import { bindKey } from 'lodash'; | ||
import { bindKey, filter } from 'lodash'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh. Good catch. Thanks! |
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,15 +3,19 @@ import { join } from 'path'; | |
|
||
export default class UiNavLink { | ||
constructor(uiExports, spec) { | ||
this.id = spec.id; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there are other things that rely on this property, can you add a test to verify that the constructor sets it properly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
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']); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class has a couple other wrinkles to its logic that I think are worth testing / documenting via tests:
uiExports.urlBasePath
defaults to an empty string if it's falsy, which affects the UiNavLink instance'surl
prop.order
defaults to 0.linkToLastSubUrl
will be false only ifspec.linkToLastSubUrl
is false, but will default to true for any other value (including falsy ones!).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.