mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
BWC link fixes (#11062)
This commit is contained in:
parent
def2a7ecf3
commit
7604c5f8f6
17 changed files with 111 additions and 103 deletions
|
@ -80,7 +80,13 @@ module.exports = function (kibana) {
|
|||
id: 'kibana:dashboard',
|
||||
title: 'Dashboard',
|
||||
order: -1001,
|
||||
url: `${kbnBaseUrl}#/dashboard`,
|
||||
url: `${kbnBaseUrl}#/dashboards`,
|
||||
// The subUrlBase is the common substring of all urls for this app. If not given, it defaults to the url
|
||||
// above. This app has to use a different subUrlBase, in addition to the url above, because "#/dashboard"
|
||||
// routes to a page that creates a new dashboard. When we introduced a landing page, we needed to change
|
||||
// the url above in order to preserve the original url for BWC. The subUrlBase helps the Chrome api nav
|
||||
// to determine what url to use for the app link.
|
||||
subUrlBase: `${kbnBaseUrl}#/dashboard`,
|
||||
description: 'compose visualizations for much win',
|
||||
icon: 'plugins/kibana/assets/dashboard.svg',
|
||||
}, {
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
<div
|
||||
data-transclude-slot="topLeftCorner"
|
||||
>
|
||||
<!-- Breadcrumbs. -->
|
||||
<bread-crumbs
|
||||
page-title="getDashTitle()"
|
||||
use-links="true"
|
||||
omit-current-page="true"
|
||||
></bread-crumbs>
|
||||
<div class="kuiLocalBreadcrumbs" data-test-subj="breadcrumbs">
|
||||
<div class="kuiLocalBreadcrumb">
|
||||
<a class="kuiLocalBreadcrumb__link" href="{{landingPageUrl()}}">Dashboard</a>
|
||||
</div>
|
||||
<div class="kuiLocalBreadcrumb">
|
||||
{{ getDashTitle() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search. -->
|
||||
|
|
|
@ -13,12 +13,14 @@ import DocTitleProvider from 'ui/doc_title';
|
|||
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
|
||||
import { getTopNavConfig } from './top_nav/get_top_nav_config';
|
||||
import { createPanelState } from 'plugins/kibana/dashboard/panel/panel_state';
|
||||
import { DashboardConstants } from './dashboard_constants';
|
||||
import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants';
|
||||
import { VisualizeConstants } from 'plugins/kibana/visualize/visualize_constants';
|
||||
import UtilsBrushEventProvider from 'ui/utils/brush_event';
|
||||
import FilterBarFilterBarClickHandlerProvider from 'ui/filter_bar/filter_bar_click_handler';
|
||||
import { FilterUtils } from './filter_utils';
|
||||
import { getPersistedStateId } from 'plugins/kibana/dashboard/panel/panel_state';
|
||||
import errors from 'ui/errors';
|
||||
import notify from 'ui/notify';
|
||||
|
||||
const app = uiModules.get('app/dashboard', [
|
||||
'elasticsearch',
|
||||
|
@ -30,24 +32,37 @@ const app = uiModules.get('app/dashboard', [
|
|||
]);
|
||||
|
||||
uiRoutes
|
||||
.when('/dashboard/create', {
|
||||
.when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {
|
||||
template: dashboardTemplate,
|
||||
resolve: {
|
||||
dash: function (savedDashboards, courier) {
|
||||
return savedDashboards.get()
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'dashboard': '/dashboard'
|
||||
'dashboard': DashboardConstants.LANDING_PAGE_PATH
|
||||
}));
|
||||
}
|
||||
}
|
||||
})
|
||||
.when('/dashboard/:id', {
|
||||
.when(createDashboardEditUrl(':id'), {
|
||||
template: dashboardTemplate,
|
||||
resolve: {
|
||||
dash: function (savedDashboards, Notifier, $route, $location, courier) {
|
||||
return savedDashboards.get($route.current.params.id)
|
||||
dash: function (savedDashboards, Notifier, $route, $location, courier, kbnUrl, AppState) {
|
||||
const id = $route.current.params.id;
|
||||
return savedDashboards.get(id)
|
||||
.catch((error) => {
|
||||
// Preserve BWC of v5.3.0 links for new, unsaved dashboards.
|
||||
// See https://github.com/elastic/kibana/issues/10951 for more context.
|
||||
if (error instanceof errors.SavedObjectNotFound && id === 'create') {
|
||||
// Note "new AppState" is neccessary so the state in the url is preserved through the redirect.
|
||||
kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState());
|
||||
notify.error(
|
||||
'The url "dashboard/create" is deprecated and will be removed in 6.0. Please update your bookmarks.');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'dashboard' : '/dashboard'
|
||||
'dashboard' : DashboardConstants.LANDING_PAGE_PATH
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +114,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
|||
$scope.$watch('state.options.darkTheme', setDarkTheme);
|
||||
|
||||
$scope.topNavMenu = getTopNavConfig(kbnUrl);
|
||||
$scope.landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`;
|
||||
|
||||
$scope.refresh = _.bindKey(courier, 'fetch');
|
||||
|
||||
|
@ -200,6 +216,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
|||
$state.save();
|
||||
};
|
||||
|
||||
|
||||
$scope.brushEvent = brushEvent;
|
||||
$scope.filterBarClickHandler = filterBarClickHandler;
|
||||
$scope.expandedPanel = null;
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import { encodeQueryComponent } from '../../../../utils';
|
||||
|
||||
export const DashboardConstants = {
|
||||
ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard',
|
||||
NEW_VISUALIZATION_ID_PARAM: 'addVisualization',
|
||||
LANDING_PAGE_PATH: '/dashboard'
|
||||
LANDING_PAGE_PATH: '/dashboards',
|
||||
CREATE_NEW_DASHBOARD_URL: '/dashboard',
|
||||
};
|
||||
|
||||
export function createDashboardEditUrl(id) {
|
||||
return `/dashboard/${encodeQueryComponent(id)}`;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<!-- Create dashboard button -->
|
||||
<a
|
||||
class="kuiButton kuiButton--primary"
|
||||
href="#/dashboard/create"
|
||||
href="{{listingController.getCreateDashboardHref()}}"
|
||||
aria-label="Create new dashboard"
|
||||
data-test-subj="newDashboardLink"
|
||||
ng-if="listingController.getSelectedItemsCount() === 0"
|
||||
|
@ -97,7 +97,7 @@
|
|||
<div class="kuiPromptForItems__actions">
|
||||
<a
|
||||
class="kuiButton kuiButton--primary kuiButton--iconText"
|
||||
href="#/dashboard/create"
|
||||
href="{{listingController.getCreateDashboardHref()}}"
|
||||
>
|
||||
<span class="kuiButton__icon kuiIcon fa-plus"></span>
|
||||
Create a dashboard
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import SavedObjectRegistryProvider from 'ui/saved_objects/saved_object_registry';
|
||||
import 'ui/pager_control';
|
||||
import 'ui/pager';
|
||||
import { DashboardConstants } from '../dashboard_constants';
|
||||
import _ from 'lodash';
|
||||
import { DashboardConstants, createDashboardEditUrl } from '../dashboard_constants';
|
||||
|
||||
export function DashboardListingController($injector, $scope) {
|
||||
const $filter = $injector.get('$filter');
|
||||
|
@ -147,6 +147,10 @@ export function DashboardListingController($injector, $scope) {
|
|||
};
|
||||
|
||||
this.getUrlForItem = function getUrlForItem(item) {
|
||||
return `#/dashboard/${item.id}`;
|
||||
return `#${createDashboardEditUrl(item.id)}`;
|
||||
};
|
||||
|
||||
this.getCreateDashboardHref = function getCreateDashboardHref() {
|
||||
return `#${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -135,25 +135,4 @@ describe('UiNavLink', () => {
|
|||
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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -91,9 +91,18 @@ describe('chrome nav apis', function () {
|
|||
it('injects the globalState of the current url to all links for the same app', function () {
|
||||
const appUrlStore = new StubBrowserStorage();
|
||||
const nav = [
|
||||
{ url: 'https://localhost:9200/app/kibana#discover' },
|
||||
{ url: 'https://localhost:9200/app/kibana#visualize' },
|
||||
{ url: 'https://localhost:9200/app/kibana#dashboard' },
|
||||
{
|
||||
url: 'https://localhost:9200/app/kibana#discover',
|
||||
subUrlBase: 'https://localhost:9200/app/kibana#discover'
|
||||
},
|
||||
{
|
||||
url: 'https://localhost:9200/app/kibana#visualize',
|
||||
subUrlBase: 'https://localhost:9200/app/kibana#visualize'
|
||||
},
|
||||
{
|
||||
url: 'https://localhost:9200/app/kibana#dashboards',
|
||||
subUrlBase: 'https://localhost:9200/app/kibana#dashboard'
|
||||
},
|
||||
].map(l => {
|
||||
l.lastSubUrl = l.url;
|
||||
return l;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { parse, format } from 'url';
|
||||
import { startsWith, isString, find } from 'lodash';
|
||||
import { isString } from 'lodash';
|
||||
|
||||
export default function (chrome, internals) {
|
||||
chrome.getNavLinks = function () {
|
||||
|
@ -110,7 +110,7 @@ export default function (chrome, internals) {
|
|||
const { appId, globalState: newGlobalState } = decodeKibanaUrl(url);
|
||||
|
||||
for (const link of internals.nav) {
|
||||
link.active = startsWith(url, link.url);
|
||||
link.active = url.startsWith(link.subUrlBase);
|
||||
if (link.active) {
|
||||
setLastUrl(link, url);
|
||||
continue;
|
||||
|
@ -124,11 +124,16 @@ export default function (chrome, internals) {
|
|||
}
|
||||
};
|
||||
|
||||
internals.nav.forEach(link => {
|
||||
function relativeToAbsolute(url) {
|
||||
// convert all link urls to absolute urls
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', link.url);
|
||||
link.url = a.href;
|
||||
a.setAttribute('href', url);
|
||||
return a.href;
|
||||
}
|
||||
|
||||
internals.nav.forEach(link => {
|
||||
link.url = relativeToAbsolute(link.url);
|
||||
link.subUrlBase = relativeToAbsolute(link.subUrlBase);
|
||||
});
|
||||
|
||||
// simulate a possible change in url to initialize the
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="kuiLocalBreadcrumbs" data-test-subj="breadcrumbs">
|
||||
<div class="kuiLocalBreadcrumb" ng-if="useLinks && (!omitPages || !omitPages.includes(breadcrumb.path))" ng-repeat="breadcrumb in breadcrumbs">
|
||||
<a class=kuiLocalBreadcrumb__link" href="{{breadcrumb.url}}">{{breadcrumb.title}}</a>
|
||||
<a class="kuiLocalBreadcrumb__link" href="{{breadcrumb.url}}">{{breadcrumb.title}}</a>
|
||||
</div>
|
||||
<div class="kuiLocalBreadcrumb" ng-if="!useLinks && (!omitPages || !omitPages.includes(breadcrumb.path))" ng-repeat="breadcrumb in breadcrumbs">
|
||||
{{ breadcrumb.title }}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { encodeQueryComponent } from '../../../utils';
|
||||
|
||||
const qs = {};
|
||||
|
||||
/*****
|
||||
|
@ -12,26 +14,6 @@ function tryDecodeURIComponent(value) {
|
|||
catch (e) {} // eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
|
||||
* method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
|
||||
* encoded per http://tools.ietf.org/html/rfc3986:
|
||||
* query = *( pchar / "/" / "?" )
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
function encodeUriQuery(val, pctEncodeSpaces) {
|
||||
return encodeURIComponent(val)
|
||||
.replace(/%40/gi, '@')
|
||||
.replace(/%3A/gi, ':')
|
||||
.replace(/%24/g, '$')
|
||||
.replace(/%2C/gi, ',')
|
||||
.replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an escaped url query string into key-value pairs.
|
||||
* @returns {Object.<string,boolean|Array>}
|
||||
|
@ -82,7 +64,7 @@ qs.encode = function (obj) {
|
|||
};
|
||||
|
||||
qs.param = function (key, val) {
|
||||
return encodeUriQuery(key, true) + (val === true ? '' : '=' + encodeUriQuery(val, true));
|
||||
return encodeQueryComponent(key, true) + (val === true ? '' : '=' + encodeQueryComponent(val, true));
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { pick } from 'lodash';
|
||||
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.subUrlBase = `${uiExports.urlBasePath || ''}${spec.subUrlBase || spec.url}`;
|
||||
this.description = spec.description;
|
||||
this.icon = spec.icon;
|
||||
this.linkToLastSubUrl = spec.linkToLastSubUrl === false ? false : true;
|
||||
|
@ -14,8 +12,4 @@ export default class UiNavLink {
|
|||
this.disabled = spec.disabled || false;
|
||||
this.tooltip = spec.tooltip || '';
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return pick(this, ['id', 'title', 'url', 'order', 'description', 'icon', 'linkToLastSubUrl', 'hidden', 'disabled', 'tooltip']);
|
||||
}
|
||||
}
|
||||
|
|
19
src/utils/encode_query_component.js
Normal file
19
src/utils/encode_query_component.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
|
||||
* method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
|
||||
* encoded per http://tools.ietf.org/html/rfc3986:
|
||||
* query = *( pchar / "/" / "?" )
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* pct-encoded = "%" HEXDIG HEXDIG
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
export function encodeQueryComponent(val, pctEncodeSpaces = false) {
|
||||
return encodeURIComponent(val)
|
||||
.replace(/%40/gi, '@')
|
||||
.replace(/%3A/gi, ':')
|
||||
.replace(/%24/g, '$')
|
||||
.replace(/%2C/gi, ',')
|
||||
.replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
||||
}
|
|
@ -4,3 +4,5 @@ export deepCloneWithBuffers from './deep_clone_with_buffers';
|
|||
export fromRoot from './from_root';
|
||||
export pkg from './package_json';
|
||||
export unset from './unset';
|
||||
|
||||
export { encodeQueryComponent } from './encode_query_component';
|
||||
|
|
|
@ -1,22 +1,5 @@
|
|||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { dirname } from 'path';
|
||||
|
||||
let packageDir;
|
||||
let packagePath;
|
||||
|
||||
while (!packagePath || !existsSync(packagePath)) {
|
||||
const prev = packageDir;
|
||||
packageDir = prev ? join(prev, '..') : __dirname;
|
||||
packagePath = join(packageDir, 'package.json');
|
||||
|
||||
if (prev === packageDir) {
|
||||
// if going up a directory doesn't work, we
|
||||
// are already at the root of the filesystem
|
||||
throw new Error('unable to find package.json');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = require(packagePath);
|
||||
module.exports.__filename = packagePath;
|
||||
module.exports.__dirname = packageDir;
|
||||
module.exports = require('../../package.json');
|
||||
module.exports.__filename = require.resolve('../../package.json');
|
||||
module.exports.__dirname = dirname(module.exports.__filename);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
const shield = require('./shield');
|
||||
|
||||
const kibanaURL = '/app/kibana';
|
||||
|
||||
module.exports = {
|
||||
|
@ -40,7 +39,7 @@ module.exports = {
|
|||
},
|
||||
dashboard: {
|
||||
pathname: kibanaURL,
|
||||
hash: '/dashboard',
|
||||
hash: '/dashboards',
|
||||
},
|
||||
settings: {
|
||||
pathname: kibanaURL,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defaultFindTimeout } from '../';
|
||||
|
||||
import {
|
||||
defaultFindTimeout,
|
||||
} from '../';
|
||||
DashboardConstants
|
||||
} from '../../../src/core_plugins/kibana/public/dashboard/dashboard_constants';
|
||||
|
||||
import PageObjects from './';
|
||||
|
||||
|
@ -14,7 +15,7 @@ export default class DashboardPage {
|
|||
|
||||
gotoDashboardLandingPage() {
|
||||
return this.findTimeout
|
||||
.findByCssSelector('a[href="#/dashboard"]')
|
||||
.findByCssSelector(`a[href="#${DashboardConstants.LANDING_PAGE_PATH}"]`)
|
||||
.click();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue