Switch to core application service (#63443) (#66353)

This commit is contained in:
Joe Reuter 2020-05-13 11:34:57 +02:00 committed by GitHub
parent 6ec9af5e37
commit ef2c5ef761
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
339 changed files with 2239 additions and 1706 deletions

View file

@ -29,7 +29,7 @@ The API returns the following:
"id": "discover",
"name": "Discover",
"icon": "discoverApp",
"navLinkId": "kibana:discover",
"navLinkId": "discover",
"app": [
"kibana"
],
@ -74,7 +74,7 @@ The API returns the following:
"id": "visualize",
"name": "Visualize",
"icon": "visualizeApp",
"navLinkId": "kibana:visualize",
"navLinkId": "visualize",
"app": [
"kibana"
],
@ -121,7 +121,7 @@ The API returns the following:
"id": "dashboard",
"name": "Dashboard",
"icon": "dashboardApp",
"navLinkId": "kibana:dashboard",
"navLinkId": "dashboards",
"app": [
"kibana"
],
@ -173,7 +173,7 @@ The API returns the following:
"id": "dev_tools",
"name": "Dev Tools",
"icon": "devToolsApp",
"navLinkId": "kibana:dev_tools",
"navLinkId": "dev_tools",
"app": [
"kibana"
],

View file

@ -153,7 +153,7 @@ init(server) {
defaultMessage: 'Dev Tools',
}),
icon: 'devToolsApp',
navLinkId: 'kibana:dev_tools',
navLinkId: 'dev_tools',
app: ['kibana'],
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
privileges: {
@ -216,7 +216,7 @@ init(server) {
}),
order: 100,
icon: 'discoverApp',
navLinkId: 'kibana:discover',
navLinkId: 'discover',
app: ['kibana'],
catalogue: ['discover'],
privileges: {

View file

@ -174,7 +174,7 @@ to view an embedded dashboard.
* Generate a PNG report
TIP: To create a link to a dashboard by title, use: +
`${domain}/${basepath?}/app/kibana#/dashboards?title=${yourdashboardtitle}`
`${domain}/${basepath?}/app/dashboards#/list?title=${yourdashboardtitle}`
TIP: When sharing a link to a dashboard snapshot, use the *Short URL*. Snapshot
URLs are long and can be problematic for Internet Explorer and other

View file

@ -192,7 +192,7 @@ export class ChromeService {
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
homeHref={http.basePath.prepend('/app/kibana#/home')}
homeHref={http.basePath.prepend('/app/home')}
isVisible$={this.isVisible$}
kibanaVersion={injectedMetadata.getKibanaVersion()}
legacyMode={injectedMetadata.getLegacyMode()}

View file

@ -116,6 +116,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = `
},
]
}
navigateToApp={[Function]}
onIsLockedUpdate={[Function]}
onIsOpenUpdate={[Function]}
recentNavLinks={
@ -2993,6 +2994,7 @@ exports[`CollapsibleNav renders the default nav 1`] = `
isLocked={false}
isOpen={false}
navLinks={Array []}
navigateToApp={[Function]}
onIsLockedUpdate={[Function]}
onIsOpenUpdate={[Function]}
recentNavLinks={Array []}
@ -3023,6 +3025,7 @@ exports[`CollapsibleNav renders the default nav 2`] = `
isLocked={false}
isOpen={true}
navLinks={Array []}
navigateToApp={[Function]}
onIsLockedUpdate={[Function]}
onIsOpenUpdate={[Function]}
recentNavLinks={Array []}
@ -3589,6 +3592,7 @@ exports[`CollapsibleNav renders the default nav 3`] = `
isLocked={true}
isOpen={true}
navLinks={Array []}
navigateToApp={[Function]}
onIsLockedUpdate={[Function]}
onIsOpenUpdate={[Function]}
recentNavLinks={Array []}

View file

@ -63,6 +63,7 @@ function mockProps() {
storage: new StubBrowserStorage(),
onIsOpenUpdate: () => {},
onIsLockedUpdate: () => {},
navigateToApp: () => {},
};
}

View file

@ -78,6 +78,7 @@ interface Props {
storage?: Storage;
onIsLockedUpdate: OnIsLockedUpdate;
onIsOpenUpdate: (isOpen?: boolean) => void;
navigateToApp: (appId: string) => void;
}
export function CollapsibleNav({
@ -89,6 +90,7 @@ export function CollapsibleNav({
onIsOpenUpdate,
homeHref,
id,
navigateToApp,
storage = window.localStorage,
}: Props) {
const lockRef = useRef<HTMLButtonElement>(null);
@ -124,7 +126,19 @@ export function CollapsibleNav({
label: 'Home',
iconType: 'home',
href: homeHref,
onClick: () => onIsOpenUpdate(false),
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
onIsOpenUpdate(false);
if (
event.isDefaultPrevented() ||
event.altKey ||
event.metaKey ||
event.ctrlKey
) {
return;
}
event.preventDefault();
navigateToApp('home');
},
},
]}
maxWidth="none"

View file

@ -247,6 +247,7 @@ export class Header extends Component<HeaderProps, State> {
href={this.props.homeHref}
forceNavigation={this.state.forceNavigation}
navLinks={navLinks}
navigateToApp={this.props.application.navigateToApp}
/>
</EuiHeaderSectionItem>
@ -287,6 +288,7 @@ export class Header extends Component<HeaderProps, State> {
this.toggleCollapsibleNavRef.current.focus();
}
}}
navigateToApp={this.props.application.navigateToApp}
/>
) : (
// TODO #64541

View file

@ -41,7 +41,8 @@ function findClosestAnchor(element: HTMLElement): HTMLAnchorElement | void {
function onClick(
event: React.MouseEvent<HTMLAnchorElement>,
forceNavigation: boolean,
navLinks: NavLink[]
navLinks: NavLink[],
navigateToApp: (appId: string) => void
) {
const anchor = findClosestAnchor((event as any).nativeEvent.target);
if (!anchor) {
@ -54,32 +55,31 @@ function onClick(
return;
}
if (
!forceNavigation ||
event.isDefaultPrevented() ||
event.altKey ||
event.metaKey ||
event.ctrlKey
) {
if (event.isDefaultPrevented() || event.altKey || event.metaKey || event.ctrlKey) {
return;
}
const toParsed = Url.parse(anchor.href);
const fromParsed = Url.parse(document.location.href);
const sameProto = toParsed.protocol === fromParsed.protocol;
const sameHost = toParsed.host === fromParsed.host;
const samePath = toParsed.path === fromParsed.path;
if (forceNavigation) {
const toParsed = Url.parse(anchor.href);
const fromParsed = Url.parse(document.location.href);
const sameProto = toParsed.protocol === fromParsed.protocol;
const sameHost = toParsed.host === fromParsed.host;
const samePath = toParsed.path === fromParsed.path;
if (sameProto && sameHost && samePath) {
if (toParsed.hash) {
document.location.reload();
if (sameProto && sameHost && samePath) {
if (toParsed.hash) {
document.location.reload();
}
// event.preventDefault() keeps the browser from seeing the new url as an update
// and even setting window.location does not mimic that behavior, so instead
// we use stopPropagation() to prevent angular from seeing the click and
// starting a digest cycle/attempting to handle it in the router.
event.stopPropagation();
}
// event.preventDefault() keeps the browser from seeing the new url as an update
// and even setting window.location does not mimic that behavior, so instead
// we use stopPropagation() to prevent angular from seeing the click and
// starting a digest cycle/attempting to handle it in the router.
event.stopPropagation();
} else {
navigateToApp('home');
event.preventDefault();
}
}
@ -87,14 +87,15 @@ interface Props {
href: string;
navLinks: NavLink[];
forceNavigation: boolean;
navigateToApp: (appId: string) => void;
}
export function HeaderLogo({ href, forceNavigation, navLinks }: Props) {
export function HeaderLogo({ href, forceNavigation, navLinks, navigateToApp }: Props) {
return (
<EuiHeaderLogo
data-test-subj="logo"
iconType="logoElastic"
onClick={e => onClick(e, forceNavigation, navLinks)}
onClick={e => onClick(e, forceNavigation, navLinks, navigateToApp)}
href={href}
aria-label={i18n.translate('core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel', {
defaultMessage: 'Go to home page',

View file

@ -49,7 +49,7 @@ describe('default route provider', () => {
expect(status).toEqual(302);
expect(header).toMatchObject({
location: '/hello/app/kibana',
location: '/hello/app/home',
});
});
@ -71,7 +71,7 @@ describe('default route provider', () => {
const { status, header } = await kbnTestServer.request.get(root, '/');
expect(status).toEqual(302);
expect(header).toMatchObject({
location: '/hello/app/kibana',
location: '/hello/app/home',
});
});

View file

@ -52,7 +52,6 @@ export default function(kibana) {
},
uiExports: {
hacks: ['plugins/kibana/dev_tools'],
app: {
id: 'kibana',
title: 'Kibana',
@ -61,49 +60,6 @@ export default function(kibana) {
},
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
links: [
{
id: 'kibana:discover',
title: i18n.translate('kbn.discoverTitle', {
defaultMessage: 'Discover',
}),
order: 2000,
url: `${kbnBaseUrl}#/discover`,
euiIconType: 'discoverApp',
disableSubUrlTracking: true,
category: DEFAULT_APP_CATEGORIES.kibana,
},
{
id: 'kibana:visualize',
title: i18n.translate('kbn.visualizeTitle', {
defaultMessage: 'Visualize',
}),
order: 7000,
url: `${kbnBaseUrl}#/visualize`,
euiIconType: 'visualizeApp',
disableSubUrlTracking: true,
category: DEFAULT_APP_CATEGORIES.kibana,
},
{
id: 'kibana:dashboard',
title: i18n.translate('kbn.dashboardTitle', {
defaultMessage: 'Dashboard',
}),
order: 1000,
url: `${kbnBaseUrl}#/dashboards`,
euiIconType: 'dashboardApp',
disableSubUrlTracking: true,
category: DEFAULT_APP_CATEGORIES.kibana,
},
{
id: 'kibana:dev_tools',
title: i18n.translate('kbn.devToolsTitle', {
defaultMessage: 'Dev Tools',
}),
order: 9001,
url: '/app/kibana#/dev_tools',
euiIconType: 'devToolsApp',
category: DEFAULT_APP_CATEGORIES.management,
},
{
id: 'kibana:stack_management',
title: i18n.translate('kbn.managementTitle', {

View file

@ -24,6 +24,8 @@ import 'ui/private';
import { pluginInstance } from './legacy';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import hits from 'fixtures/real_hits';
import { setScopedHistory } from '../../../../../../plugins/discover/public/kibana_services';
import { createBrowserHistory } from 'history';
let $parentScope;
@ -58,6 +60,7 @@ const destroy = function() {
describe('docTable', function() {
let $elem;
before(() => setScopedHistory(createBrowserHistory()));
beforeEach(() => pluginInstance.initializeInnerAngular());
beforeEach(() => pluginInstance.initializeServices());
beforeEach(ngMock.module('app/discover'));

View file

@ -25,6 +25,8 @@ import { getFakeRow, getFakeRowVals } from 'fixtures/fake_row';
import $ from 'jquery';
import { pluginInstance } from './legacy';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import { setScopedHistory } from '../../../../../../plugins/discover/public/kibana_services';
import { createBrowserHistory } from 'history';
describe('Doc Table', function() {
let $parentScope;
@ -37,6 +39,7 @@ describe('Doc Table', function() {
let stubFieldFormatConverter;
beforeEach(() => pluginInstance.initializeServices());
beforeEach(() => pluginInstance.initializeInnerAngular());
before(() => setScopedHistory(createBrowserHistory()));
beforeEach(ngMock.module('app/discover'));
beforeEach(
ngMock.inject(function($rootScope, Private) {

View file

@ -1,3 +0,0 @@
This folder is just a left-over of the things that can't be moved to Kibana platform just yet:
* Check whether there are no dev tools and hide the link in the nav bar (this can be moved as soon as all dev tools are moved)

View file

@ -11,9 +11,6 @@
// bad cascading in the Editor layout
@import '../../../../plugins/maps_legacy/public/index';
// Home styles
@import '../../../../plugins/home/public/application/index';
// Management styles
@import './management/index';

View file

@ -21,19 +21,15 @@
// preloading (for faster webpack builds)
import routes from 'ui/routes';
import { uiModules } from 'ui/modules';
import { npSetup } from 'ui/new_platform';
// import the uiExports that we want to "use"
import 'uiExports/home';
import 'uiExports/visualize';
import 'uiExports/savedObjectTypes';
import 'uiExports/fieldFormatEditors';
import 'uiExports/navbarExtensions';
import 'uiExports/contextMenuActions';
import 'uiExports/managementSections';
import 'uiExports/indexManagement';
import 'uiExports/docViews';
import 'uiExports/embeddableFactories';
import 'uiExports/embeddableActions';
import 'uiExports/inspectorViews';
@ -43,8 +39,6 @@ import 'uiExports/interpreter';
import 'ui/autoload/all';
import './management';
import './dev_tools';
import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public';
import { localApplicationService } from './local_application_service';
npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('doc', 'discover', { keepPrefix: true });
@ -58,7 +52,3 @@ const { config } = npSetup.plugins.kibanaLegacy;
routes.otherwise({
redirectTo: `/${config.defaultAppId || 'discover'}`,
});
uiModules
.get('kibana')
.run($location => showAppRedirectNotification($location, npSetup.core.notifications.toasts));

View file

@ -106,7 +106,9 @@ export class LocalApplicationService {
template: '<span></span>',
controller($location: ILocationService) {
const newPath = forwardDefinition.rewritePath($location.url());
npStart.core.application.navigateToApp(forwardDefinition.newAppId, { path: newPath });
window.location.replace(
npStart.core.http.basePath.prepend(`/app/${forwardDefinition.newAppId}${newPath}`)
);
},
});
});

View file

@ -111,6 +111,7 @@ exports[`CreateIndexPatternWizard renders the empty state when there are no indi
/>
<EmptyState
onRefresh={[Function]}
prependBasePath={[MockFunction]}
/>
</div>
<EuiGlobalToastList

View file

@ -19,7 +19,7 @@ exports[`EmptyState should render normally 1`] = `
values={
Object {
"getStartedLink": <ForwardRef
href="#/home/tutorial_directory/sampleData"
href="/app/home#/tutorial_directory/sampleData"
>
<FormattedMessage
defaultMessage="get started with some sample data sets."
@ -28,7 +28,7 @@ exports[`EmptyState should render normally 1`] = `
/>
</ForwardRef>,
"learnHowLink": <ForwardRef
href="#/home/tutorial_directory"
href="/app/home#/tutorial_directory"
>
<FormattedMessage
defaultMessage="Learn how"

View file

@ -24,7 +24,7 @@ import sinon from 'sinon';
describe('EmptyState', () => {
it('should render normally', () => {
const component = shallow(<EmptyState onRefresh={() => {}} />);
const component = shallow(<EmptyState onRefresh={() => {}} prependBasePath={x => x} />);
expect(component).toMatchSnapshot();
});
@ -34,7 +34,9 @@ describe('EmptyState', () => {
it('is called when refresh button is clicked', () => {
const onRefreshHandler = sinon.stub();
const component = shallow(<EmptyState onRefresh={onRefreshHandler} />);
const component = shallow(
<EmptyState onRefresh={onRefreshHandler} prependBasePath={x => x} />
);
component.find('[data-test-subj="refreshIndicesButton"]').simulate('click');

View file

@ -22,8 +22,15 @@ import React from 'react';
import { EuiCallOut, EuiTextColor, EuiLink, EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { IBasePath } from 'kibana/public';
export const EmptyState = ({ onRefresh }: { onRefresh: () => void }) => (
export const EmptyState = ({
onRefresh,
prependBasePath,
}: {
onRefresh: () => void;
prependBasePath: IBasePath['prepend'];
}) => (
<div>
<EuiCallOut
color="warning"
@ -48,7 +55,7 @@ export const EmptyState = ({ onRefresh }: { onRefresh: () => void }) => (
</EuiTextColor>
),
learnHowLink: (
<EuiLink href="#/home/tutorial_directory">
<EuiLink href={prependBasePath('/app/home#/tutorial_directory')}>
<FormattedMessage
id="kbn.management.createIndexPattern.emptyStateLabel.learnHowLink"
defaultMessage="Learn how"
@ -56,7 +63,7 @@ export const EmptyState = ({ onRefresh }: { onRefresh: () => void }) => (
</EuiLink>
),
getStartedLink: (
<EuiLink href="#/home/tutorial_directory/sampleData">
<EuiLink href={prependBasePath('/app/home#/tutorial_directory/sampleData')}>
<FormattedMessage
id="kbn.management.createIndexPattern.emptyStateLabel.getStartedLink"
defaultMessage="get started with some sample data sets."

View file

@ -57,6 +57,7 @@ const services = {
changeUrl: jest.fn(),
openConfirm: overlays.openConfirm,
indexPatternCreationType: mockIndexPatternCreationType,
prependBasePath: jest.fn(x => x),
};
describe('CreateIndexPatternWizard', () => {

View file

@ -35,6 +35,7 @@ import {
SavedObjectsClient,
IUiSettingsClient,
OverlayStart,
IBasePath,
} from '../../../../../../../../core/public';
import { DataPublicPluginStart } from '../../../../../../../../plugins/data/public';
import { IndexPatternCreationConfig } from '../../../../../../../../plugins/index_pattern_management/public';
@ -50,6 +51,7 @@ interface CreateIndexPatternWizardProps {
uiSettings: IUiSettingsClient;
changeUrl: (url: string) => void;
openConfirm: OverlayStart['openConfirm'];
prependBasePath: IBasePath['prepend'];
};
}
@ -235,7 +237,12 @@ export class CreateIndexPatternWizard extends Component<
const hasDataIndices = allIndices.some(({ name }: MatchedIndex) => !name.startsWith('.'));
if (!hasDataIndices && !isIncludingSystemIndices && !remoteClustersExist) {
return <EmptyState onRefresh={this.fetchData} />;
return (
<EmptyState
onRefresh={this.fetchData}
prependBasePath={this.props.services.prependBasePath}
/>
);
}
if (step === 1) {

View file

@ -45,6 +45,7 @@ uiRoutes.when('/management/kibana/index_pattern', {
$scope.$evalAsync(() => kbnUrl.changePath(url));
},
openConfirm: npStart.core.overlays.openConfirm,
prependBasePath: npStart.core.http.basePath.prepend,
};
const initialQuery = $routeParams.id ? decodeURIComponent($routeParams.id) : undefined;

View file

@ -85,7 +85,7 @@ const savedObjectsManagement = getManagementaMock({
},
getInAppUrl(obj) {
return {
path: `/app/kibana#/visualize/edit/${encodeURIComponent(obj.id)}`,
path: `/app/visualize#/edit/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'visualize.show',
};
},
@ -101,7 +101,7 @@ const savedObjectsManagement = getManagementaMock({
},
getInAppUrl(obj) {
return {
path: `/app/kibana#/discover/${encodeURIComponent(obj.id)}`,
path: `/app/discover#//${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'discover.show',
};
},
@ -200,7 +200,7 @@ describe('findRelationships', () => {
title: 'Foo',
editUrl: '/management/kibana/objects/savedVisualizations/1',
inAppUrl: {
path: '/app/kibana#/visualize/edit/1',
path: '/app/visualize#/edit/1',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -214,7 +214,7 @@ describe('findRelationships', () => {
title: 'Bar',
editUrl: '/management/kibana/objects/savedVisualizations/2',
inAppUrl: {
path: '/app/kibana#/visualize/edit/2',
path: '/app/visualize#/edit/2',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -228,7 +228,7 @@ describe('findRelationships', () => {
title: 'FooBar',
editUrl: '/management/kibana/objects/savedVisualizations/3',
inAppUrl: {
path: '/app/kibana#/visualize/edit/3',
path: '/app/visualize#/edit/3',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -453,7 +453,7 @@ describe('findRelationships', () => {
title: 'Foo',
editUrl: '/management/kibana/objects/savedVisualizations/1',
inAppUrl: {
path: '/app/kibana#/visualize/edit/1',
path: '/app/visualize#/edit/1',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -467,7 +467,7 @@ describe('findRelationships', () => {
title: 'Bar',
editUrl: '/management/kibana/objects/savedVisualizations/2',
inAppUrl: {
path: '/app/kibana#/visualize/edit/2',
path: '/app/visualize#/edit/2',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -481,7 +481,7 @@ describe('findRelationships', () => {
title: 'FooBar',
editUrl: '/management/kibana/objects/savedVisualizations/3',
inAppUrl: {
path: '/app/kibana#/visualize/edit/3',
path: '/app/visualize#/edit/3',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -567,7 +567,7 @@ describe('findRelationships', () => {
title: 'Foo',
editUrl: '/management/kibana/objects/savedVisualizations/1',
inAppUrl: {
path: '/app/kibana#/visualize/edit/1',
path: '/app/visualize#/edit/1',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -581,7 +581,7 @@ describe('findRelationships', () => {
title: 'Bar',
editUrl: '/management/kibana/objects/savedVisualizations/2',
inAppUrl: {
path: '/app/kibana#/visualize/edit/2',
path: '/app/visualize#/edit/2',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -595,7 +595,7 @@ describe('findRelationships', () => {
title: 'FooBar',
editUrl: '/management/kibana/objects/savedVisualizations/3',
inAppUrl: {
path: '/app/kibana#/visualize/edit/3',
path: '/app/visualize#/edit/3',
uiCapabilitiesPath: 'visualize.show',
},
},
@ -609,7 +609,7 @@ describe('findRelationships', () => {
title: 'My Saved Search',
editUrl: '/management/kibana/objects/savedSearches/1',
inAppUrl: {
path: '/app/kibana#/discover/1',
path: '/app/discover#//1',
uiCapabilitiesPath: 'discover.show',
},
},

View file

@ -69,7 +69,7 @@ export function getUiSettingDefaults() {
name: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteTitle', {
defaultMessage: 'Default route',
}),
value: '/app/kibana',
value: '/app/home',
schema: schema.string({
validate(value) {
if (!value.startsWith('/') || !isRelativeUrl(value)) {

View file

@ -11,7 +11,5 @@
@import './accessibility/index';
@import './directives/index';
@import './error_url_overflow/index';
@import './exit_full_screen/index';
@import './field_editor/index';
@import './style_compile/index';
@import '../../../plugins/management/public/components/index';

View file

@ -25,7 +25,6 @@ import '../private';
import '../promises';
import '../state_management/app_state';
import '../state_management/global_state';
import '../style_compile';
import '../url';
import '../directives/watch_multi';
import '../react_components';

View file

@ -112,66 +112,28 @@ describe('chrome nav apis', function() {
});
});
describe('internals.trackPossibleSubUrl()', function() {
it('injects the globalState of the current url to all links for the same app', function() {
const appUrlStore = new StubBrowserStorage();
fakedLinks = [
{
id: 'kibana:discover',
baseUrl: `${baseUrl}/app/kibana#discover`,
subUrlBase: '/app/kibana#discover',
legacy: true,
},
{
id: 'kibana:visualize',
baseUrl: `${baseUrl}/app/kibana#visualize`,
subUrlBase: '/app/kibana#visualize',
legacy: true,
},
{
id: 'kibana:dashboard',
baseUrl: `${baseUrl}/app/kibana#dashboards`,
subUrlBase: '/app/kibana#dashboard',
legacy: true,
},
];
const { internals } = init({ appUrlStore });
internals.trackPossibleSubUrl(`${baseUrl}/app/kibana#dashboard?_g=globalstate`);
expect(fakedLinks[0].url).to.be(`${baseUrl}/app/kibana#discover?_g=globalstate`);
expect(fakedLinks[0].active).to.be(false);
expect(fakedLinks[1].url).to.be(`${baseUrl}/app/kibana#visualize?_g=globalstate`);
expect(fakedLinks[1].active).to.be(false);
expect(fakedLinks[2].url).to.be(`${baseUrl}/app/kibana#dashboard?_g=globalstate`);
expect(fakedLinks[2].active).to.be(true);
});
});
describe('chrome.trackSubUrlForApp()', function() {
it('injects a manual app url', function() {
const appUrlStore = new StubBrowserStorage();
fakedLinks = [
{
id: 'kibana:visualize',
baseUrl: `${baseUrl}/app/kibana#visualize`,
url: `${baseUrl}/app/kibana#visualize`,
subUrlBase: '/app/kibana#visualize',
id: 'visualize',
baseUrl: `${baseUrl}/app/visualize#`,
url: `${baseUrl}/app/visualize#`,
subUrlBase: '/app/visualize#',
legacy: true,
},
];
const { chrome } = init({ appUrlStore });
const kibanaParsedUrl = absoluteToParsedUrl(
`${baseUrl}/xyz/app/kibana#visualize/1234?_g=globalstate`,
`${baseUrl}/xyz/app/visualize#/1234?_g=globalstate`,
'/xyz'
);
chrome.trackSubUrlForApp('kibana:visualize', kibanaParsedUrl);
chrome.trackSubUrlForApp('visualize', kibanaParsedUrl);
expect(
coreNavLinks.update.calledWith('kibana:visualize', {
url: `${baseUrl}/xyz/app/kibana#visualize/1234?_g=globalstate`,
coreNavLinks.update.calledWith('visualize', {
url: `${baseUrl}/xyz/app/visualize#/1234?_g=globalstate`,
})
).to.be(true);
});

View file

@ -357,9 +357,6 @@ export const npStart = {
registerRenderer: sinon.fake(),
registerType: sinon.fake(),
},
devTools: {
getSortedDevTools: () => [],
},
kibanaLegacy: {
getApps: () => [],
getForwards: () => [],

View file

@ -39,7 +39,7 @@ import {
Start as InspectorStart,
} from '../../../../plugins/inspector/public';
import { ChartsPluginSetup, ChartsPluginStart } from '../../../../plugins/charts/public';
import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/public';
import { DevToolsSetup } from '../../../../plugins/dev_tools/public';
import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public';
import { HomePublicPluginSetup } from '../../../../plugins/home/public';
import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public';
@ -105,7 +105,6 @@ export interface PluginsStart {
inspector: InspectorStart;
uiActions: UiActionsStart;
navigation: NavigationPublicPluginStart;
devTools: DevToolsStart;
kibanaLegacy: KibanaLegacyStart;
share: SharePluginStart;
management: ManagementStart;

View file

@ -64,6 +64,7 @@ export function setStartServices(npStart: NpStart) {
);
visualizationsServices.setCapabilities(npStart.core.application.capabilities);
visualizationsServices.setHttp(npStart.core.http);
visualizationsServices.setApplication(npStart.core.application);
visualizationsServices.setSavedObjects(npStart.core.savedObjects);
visualizationsServices.setIndexPatterns(npStart.plugins.data.indexPatterns);
visualizationsServices.setFilterManager(npStart.plugins.data.query.filterManager);

View file

@ -1,66 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import $ from 'jquery';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
describe('styleCompile directive', function() {
let config;
let $rootScope;
beforeEach(ngMock.module('kibana'));
beforeEach(
ngMock.inject(function($injector) {
config = $injector.get('config');
$rootScope = $injector.get('$rootScope');
})
);
it('exports a few config values as css', function() {
const $style = $('#style-compile');
config.set('truncate:maxHeight', 0);
$rootScope.$apply();
expect($style.html().trim()).to.be(
[
'.truncate-by-height {',
' max-height: none;',
' display: inline-block;',
'}',
'.truncate-by-height:before {',
' top: -15px;',
'}',
].join('\n')
);
config.set('truncate:maxHeight', 15);
$rootScope.$apply();
expect($style.html().trim()).to.be(
[
'.truncate-by-height {',
' max-height: 15px !important;',
' display: inline-block;',
'}',
'.truncate-by-height:before {',
' top: 0px;',
'}',
].join('\n')
);
});
});

View file

@ -1,3 +0,0 @@
style-compile {
display: none;
}

View file

@ -1,7 +0,0 @@
.truncate-by-height {
max-height: <%= truncateMaxHeight %>;
display: inline-block;
}
.truncate-by-height:before {
top: <%= truncateGradientTop %>;
}

View file

@ -1,52 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import $ from 'jquery';
import '../config';
import { uiModules } from '../modules';
import cssTmpl from './style_compile.css.tmpl';
const $style = $('<style>')
.appendTo('head')
.attr('id', 'style-compile');
uiModules.get('kibana').run(function($rootScope, config) {
const truncateGradientHeight = 15;
const template = _.template(cssTmpl);
const locals = {};
// watch the value of the truncate:maxHeight config param
$rootScope.$watch(
function() {
return config.get('truncate:maxHeight');
},
function(maxHeight) {
if (maxHeight > 0) {
locals.truncateMaxHeight = maxHeight + 'px !important';
locals.truncateGradientTop = maxHeight - truncateGradientHeight + 'px';
} else {
locals.truncateMaxHeight = 'none';
locals.truncateGradientTop = '-' + truncateGradientHeight + 'px';
}
$style.html(template(locals));
}
);
});

View file

@ -24,9 +24,9 @@ import { KibanaParsedUrl } from './kibana_parsed_url';
/**
*
* @param absoluteUrl - an absolute url, e.g. https://localhost:5601/gra/app/kibana#/visualize/edit/viz_id?hi=bye
* @param absoluteUrl - an absolute url, e.g. https://localhost:5601/gra/app/visualize#/edit/viz_id?hi=bye
* @param basePath - An optional base path for kibana. If supplied, should start with a "/".
* e.g. in https://localhost:5601/gra/app/kibana#/visualize/edit/viz_id the basePath is
* e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id the basePath is
* "/gra".
* @return {KibanaParsedUrl}
*/

View file

@ -25,22 +25,22 @@ import { prependPath } from './prepend_path';
interface Options {
/**
* An optional base path for kibana. If supplied, should start with a "/".
* e.g. in https://localhost:5601/gra/app/kibana#/visualize/edit/viz_id the
* e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id the
* basePath is "/gra"
*/
basePath?: string;
/**
* The app id.
* e.g. in https://localhost:5601/gra/app/kibana#/visualize/edit/viz_id the app id is "kibana".
* e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id the app id is "kibana".
*/
appId: string;
/**
* The path for a page in the the app. Should start with a "/". Don't include the hash sign. Can
* include all query parameters.
* e.g. in https://localhost:5601/gra/app/kibana#/visualize/edit/viz_id?g=state the appPath is
* "/visualize/edit/viz_id?g=state"
* e.g. in https://localhost:5601/gra/app/visualize#/edit/viz_id?g=state the appPath is
* "/edit/viz_id?g=state"
*/
appPath?: string;

View file

@ -22,7 +22,9 @@ import { i18n } from '@kbn/i18n';
import { uiModules } from '../modules';
import { AppStateProvider } from '../state_management/app_state';
uiModules.get('kibana/url').service('kbnUrl', function(Private) {
uiModules.get('kibana/url').service('kbnUrl', function(Private, $injector) {
//config is not directly used but registers global event listeners to kbnUrl to function
$injector.get('config');
return Private(KbnUrlProvider);
});

View file

@ -25,10 +25,10 @@ describe('UiNavLink', () => {
describe('constructor', () => {
it('initializes the object properties as expected', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
url: '/app/discover#/',
euiIconType: 'discoverApp',
hidden: true,
disabled: true,
@ -56,9 +56,9 @@ describe('UiNavLink', () => {
it('initializes the order property to 0 when order is not specified in the spec', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
url: '/app/kibana#/discover',
url: '/app/discover#/',
};
const link = new UiNavLink(spec);
@ -67,10 +67,10 @@ describe('UiNavLink', () => {
it('initializes the linkToLastSubUrl property to false when false is specified in the spec', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
url: '/app/discover#/',
linkToLastSubUrl: false,
};
const link = new UiNavLink(spec);
@ -80,10 +80,10 @@ describe('UiNavLink', () => {
it('initializes the linkToLastSubUrl property to true by default', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
url: '/app/discover#/',
};
const link = new UiNavLink(spec);
@ -92,10 +92,10 @@ describe('UiNavLink', () => {
it('initializes the hidden property to false by default', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
url: '/app/discover#/',
};
const link = new UiNavLink(spec);
@ -104,10 +104,10 @@ describe('UiNavLink', () => {
it('initializes the disabled property to false by default', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
url: '/app/discover#/',
};
const link = new UiNavLink(spec);
@ -116,10 +116,10 @@ describe('UiNavLink', () => {
it('initializes the tooltip property to an empty string by default', () => {
const spec = {
id: 'kibana:discover',
id: 'discover',
title: 'Discover',
order: -1003,
url: '/app/kibana#/discover',
url: '/app/discover#/',
};
const link = new UiNavLink(spec);

View file

@ -36,6 +36,9 @@ export function send(method: string, path: string, data: any) {
const options: JQuery.AjaxSettings = {
url: '../api/console/proxy?' + stringify({ path, method }, { sort: false }),
headers: {
'kbn-xsrf': 'kibana',
},
data,
contentType: getContentType(data),
cache: false,

View file

@ -155,6 +155,9 @@ export function setActiveApi(api) {
$.ajax({
url: '../api/console/api_server',
dataType: 'json', // disable automatic guessing
headers: {
'kbn-xsrf': 'kibana',
},
}).then(
function(data) {
setActiveApi(loadApisFromJson(data));

View file

@ -41,7 +41,7 @@ export class ConsoleUIPlugin implements Plugin<void, void, AppSetupUIPluginDepen
defaultMessage: 'Skip cURL and use this JSON interface to work with your data directly.',
}),
icon: 'consoleApp',
path: '/app/kibana#/dev_tools/console',
path: '/app/dev_tools#/console',
showOnHomePage: true,
category: FeatureCatalogueCategory.ADMIN,
});

View file

@ -31,6 +31,7 @@ import {
CoreStart,
SavedObjectsClientContract,
PluginInitializerContext,
ScopedHistory,
} from 'kibana/public';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import { Storage } from '../../../kibana_utils/public';
@ -43,6 +44,11 @@ import { SharePluginStart } from '../../../share/public';
import { KibanaLegacyStart, configureAppAngularModule } from '../../../kibana_legacy/public';
import { SavedObjectLoader } from '../../../saved_objects/public';
// required for i18nIdDirective
import 'angular-sanitize';
// required for ngRoute
import 'angular-route';
export interface RenderDeps {
pluginInitializerContext: PluginInitializerContext;
core: CoreStart;
@ -65,6 +71,8 @@ export interface RenderDeps {
share?: SharePluginStart;
config: KibanaLegacyStart['config'];
usageCollection?: UsageCollectionSetup;
navigateToDefaultApp: KibanaLegacyStart['navigateToDefaultApp'];
scopedHistory: () => ScopedHistory;
}
let angularModuleInstance: IModule | null = null;
@ -76,7 +84,8 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende
configureAppAngularModule(
angularModuleInstance,
{ core: deps.core, env: deps.pluginInitializerContext.env },
true
true,
deps.scopedHistory
);
initDashboardApp(angularModuleInstance, deps);
}
@ -84,11 +93,12 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende
const $injector = mountDashboardApp(appBasePath, element);
return () => {
($injector.get('kbnUrlStateStorage') as any).cancel();
$injector.get('$rootScope').$destroy();
};
};
const mainTemplate = (basePath: string) => `<div ng-view class="kbnLocalApplicationWrapper">
const mainTemplate = (basePath: string) => `<div ng-view class="dshAppContainer">
<base href="${basePath}" />
</div>`;
@ -98,7 +108,7 @@ const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react'];
function mountDashboardApp(appBasePath: string, element: HTMLElement) {
const mountpoint = document.createElement('div');
mountpoint.setAttribute('class', 'kbnLocalApplicationWrapper');
mountpoint.setAttribute('class', 'dshAppContainer');
// eslint-disable-next-line
mountpoint.innerHTML = mainTemplate(appBasePath);
// bootstrap angular into detached element and attach it later to

View file

@ -90,10 +90,16 @@ export function initDashboardApp(app, deps) {
};
$routeProvider
.when('/', {
redirectTo: DashboardConstants.LANDING_PAGE_PATH,
})
.when(DashboardConstants.LANDING_PAGE_PATH, {
...defaults,
template: dashboardListingTemplate,
controller: function($scope, kbnUrlStateStorage, history) {
deps.core.chrome.docTitle.change(
i18n.translate('dashboard.dashboardPageTitle', { defaultMessage: 'Dashboards' })
);
const service = deps.savedDashboards;
const dashboardConfig = deps.dashboardConfig;
@ -178,6 +184,7 @@ export function initDashboardApp(app, deps) {
.catch(
redirectWhenMissing({
history,
navigateToApp: deps.core.application.navigateToApp,
mapping: {
dashboard: DashboardConstants.LANDING_PAGE_PATH,
},
@ -236,6 +243,7 @@ export function initDashboardApp(app, deps) {
.catch(
redirectWhenMissing({
history,
navigateToApp: deps.core.application.navigateToApp,
mapping: {
dashboard: DashboardConstants.LANDING_PAGE_PATH,
},
@ -245,11 +253,11 @@ export function initDashboardApp(app, deps) {
},
},
})
.when(`dashboard/:tail*?`, {
redirectTo: `/${deps.config.defaultAppId}`,
})
.when(`dashboards/:tail*?`, {
redirectTo: `/${deps.config.defaultAppId}`,
.otherwise({
template: '<span></span>',
controller: function() {
deps.navigateToDefaultApp();
},
});
});
}

View file

@ -21,24 +21,24 @@ import { getDashboardIdFromUrl } from './url';
test('getDashboardIdFromUrl', () => {
let url =
"http://localhost:5601/wev/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
"http://localhost:5601/wev/app/dashboards#/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getDashboardIdFromUrl(url)).toEqual(undefined);
url =
"http://localhost:5601/wev/app/kibana#/dashboard/625357282?_a=(description:'',filters:!()&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";
"http://localhost:5601/wev/app/dashboards#/view/625357282?_a=(description:'',filters:!()&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))";
expect(getDashboardIdFromUrl(url)).toEqual('625357282');
url = 'http://myserver.mydomain.com:5601/wev/app/kibana#/dashboard/777182';
url = 'http://myserver.mydomain.com:5601/wev/app/dashboards#/view/777182';
expect(getDashboardIdFromUrl(url)).toEqual('777182');
url =
"http://localhost:5601/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
"http://localhost:5601/app/dashboards#/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!()";
expect(getDashboardIdFromUrl(url)).toEqual(undefined);
url = '/dashboard/test/?_g=(refreshInterval:';
url = '/view/test/?_g=(refreshInterval:';
expect(getDashboardIdFromUrl(url)).toEqual('test');
url = 'dashboard/test/?_g=(refreshInterval:';
url = 'view/test/?_g=(refreshInterval:';
expect(getDashboardIdFromUrl(url)).toEqual('test');
url = '/other-app/test/';

View file

@ -27,7 +27,7 @@
* output: 39292992
*/
export function getDashboardIdFromUrl(url: string): string | undefined {
const [, dashboardId] = url.match(/dashboard\/(.*?)(\/|\?|$)/) ?? [
const [, dashboardId] = url.match(/view\/(.*?)(\/|\?|$)/) ?? [
undefined, // full match
undefined, // group with dashboardId
];

View file

@ -102,7 +102,7 @@ exports[`after fetch initialFilter 1`] = `
values={
Object {
"sampleDataInstallLink": <ForwardRef
href="#/home/tutorial_directory/sampleData"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Install some sample data"
@ -203,7 +203,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = `
values={
Object {
"sampleDataInstallLink": <ForwardRef
href="#/home/tutorial_directory/sampleData"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Install some sample data"
@ -304,7 +304,7 @@ exports[`after fetch renders table rows 1`] = `
values={
Object {
"sampleDataInstallLink": <ForwardRef
href="#/home/tutorial_directory/sampleData"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Install some sample data"
@ -405,7 +405,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `
values={
Object {
"sampleDataInstallLink": <ForwardRef
href="#/home/tutorial_directory/sampleData"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Install some sample data"
@ -506,7 +506,7 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = `
values={
Object {
"sampleDataInstallLink": <ForwardRef
href="#/home/tutorial_directory/sampleData"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Install some sample data"

View file

@ -112,7 +112,13 @@ export class DashboardListing extends React.Component {
defaultMessage="New to Kibana? {sampleDataInstallLink} to take a test drive."
values={{
sampleDataInstallLink: (
<EuiLink href="#/home/tutorial_directory/sampleData">
<EuiLink
onClick={() =>
this.props.core.application.navigateTo('home', {
path: '#/tutorial_directory/sampleData',
})
}
>
<FormattedMessage
id="dashboard.listing.createNewDashboard.sampleDataInstallLinkText"
defaultMessage="Install some sample data"

View file

@ -18,8 +18,8 @@
*/
export const DashboardConstants = {
LANDING_PAGE_PATH: '/dashboards',
CREATE_NEW_DASHBOARD_URL: '/dashboard',
LANDING_PAGE_PATH: '/list',
CREATE_NEW_DASHBOARD_URL: '/create',
ADD_EMBEDDABLE_ID: 'addEmbeddableId',
ADD_EMBEDDABLE_TYPE: 'addEmbeddableType',
DASHBOARDS_ID: 'dashboards',
@ -27,5 +27,5 @@ export const DashboardConstants = {
};
export function createDashboardEditUrl(id: string) {
return `/dashboard/${id}`;
return `/view/${id}`;
}

View file

@ -30,6 +30,8 @@ import {
PluginInitializerContext,
Plugin,
SavedObjectsClientContract,
AppUpdater,
ScopedHistory,
} from 'src/core/public';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
import {
@ -58,12 +60,12 @@ import {
} from '../../../plugins/kibana_react/public';
import { createKbnUrlTracker, Storage } from '../../../plugins/kibana_utils/public';
import {
AngularRenderedAppUpdater,
KibanaLegacySetup,
KibanaLegacyStart,
initAngularBootstrap,
} from '../../../plugins/kibana_legacy/public';
import { FeatureCatalogueCategory, HomePublicPluginSetup } from '../../../plugins/home/public';
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
import {
DashboardContainerFactory,
@ -85,6 +87,7 @@ import {
} from './url_generator';
import { createSavedDashboardLoader } from './saved_dashboards';
import { DashboardConstants } from './dashboard_constants';
import { addEmbeddableToDashboardUrl } from './url_utils/url_helper';
import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder';
declare module '../../share/public' {
@ -120,6 +123,10 @@ export type Setup = void;
export interface DashboardStart {
getSavedDashboardLoader: () => SavedObjectLoader;
addEmbeddableToDashboard: (options: {
embeddableId: string;
embeddableType: string;
}) => void | undefined;
dashboardUrlGenerator?: DashboardUrlGenerator;
}
@ -135,8 +142,10 @@ export class DashboardPlugin
implements Plugin<Setup, DashboardStart, SetupDependencies, StartDependencies> {
constructor(private initializerContext: PluginInitializerContext) {}
private appStateUpdater = new BehaviorSubject<AngularRenderedAppUpdater>(() => ({}));
private appStateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
private stopUrlTracking: (() => void) | undefined = undefined;
private getActiveUrl: (() => string) | undefined = undefined;
private currentHistory: ScopedHistory | undefined = undefined;
private dashboardUrlGenerator?: DashboardUrlGenerator;
@ -154,7 +163,7 @@ export class DashboardPlugin
createDashboardUrlGenerator(async () => {
const [coreStart, , selfStart] = await startServices;
return {
appBasePath: coreStart.application.getUrlForApp('dashboard'),
appBasePath: coreStart.application.getUrlForApp('dashboards'),
useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'),
savedDashboardLoader: selfStart.getSavedDashboardLoader(),
};
@ -195,16 +204,9 @@ export class DashboardPlugin
const placeholderFactory = new PlaceholderEmbeddableFactory();
embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory);
const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({
baseUrl: core.http.basePath.prepend('/app/kibana'),
const { appMounted, appUnMounted, stop: stopUrlTracker, getActiveUrl } = createKbnUrlTracker({
baseUrl: core.http.basePath.prepend('/app/dashboards'),
defaultSubUrl: `#${DashboardConstants.LANDING_PAGE_PATH}`,
shouldTrackUrlUpdate: pathname => {
const targetAppName = pathname.split('/')[1];
return (
targetAppName === DashboardConstants.DASHBOARDS_ID ||
targetAppName === DashboardConstants.DASHBOARD_ID
);
},
storageKey: `lastUrl:${core.http.basePath.get()}:dashboard`,
navLinkUpdater$: this.appStateUpdater,
toastNotifications: core.notifications.toasts,
@ -222,30 +224,39 @@ export class DashboardPlugin
),
},
],
getHistory: () => this.currentHistory!,
});
this.getActiveUrl = getActiveUrl;
this.stopUrlTracking = () => {
stopUrlTracker();
};
const app: App = {
id: '',
title: 'Dashboards',
id: DashboardConstants.DASHBOARDS_ID,
title: 'Dashboard',
order: -1001,
euiIconType: 'dashboardApp',
defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`,
updater$: this.appStateUpdater,
category: DEFAULT_APP_CATEGORIES.kibana,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart, dashboardStart] = await core.getStartServices();
this.currentHistory = params.history;
appMounted();
const {
embeddable: embeddableStart,
navigation,
share: shareStart,
data: dataStart,
kibanaLegacy: { dashboardConfig },
kibanaLegacy: { dashboardConfig, navigateToDefaultApp },
} = pluginsStart;
const deps: RenderDeps = {
pluginInitializerContext: this.initializerContext,
core: coreStart,
dashboardConfig,
navigateToDefaultApp,
navigation,
share: shareStart,
data: dataStart,
@ -264,10 +275,12 @@ export class DashboardPlugin
},
localStorage: new Storage(localStorage),
usageCollection,
scopedHistory: () => this.currentHistory!,
};
// make sure the index pattern list is up to date
await dataStart.indexPatterns.clearCache();
const { renderApp } = await import('./application/application');
params.element.classList.add('dshAppContainer');
const unmount = renderApp(params.element, params.appBasePath, deps);
return () => {
unmount();
@ -278,14 +291,33 @@ export class DashboardPlugin
initAngularBootstrap();
kibanaLegacy.registerLegacyApp({
...app,
id: DashboardConstants.DASHBOARD_ID,
// only register the updater in once app, otherwise all updates would happen twice
updater$: this.appStateUpdater.asObservable(),
navLinkId: 'kibana:dashboard',
});
kibanaLegacy.registerLegacyApp({ ...app, id: DashboardConstants.DASHBOARDS_ID });
core.application.register(app);
kibanaLegacy.forwardApp(
DashboardConstants.DASHBOARD_ID,
DashboardConstants.DASHBOARDS_ID,
path => {
const [, id, tail] = /dashboard\/?(.*?)($|\?.*)/.exec(path) || [];
if (!id && !tail) {
// unrecognized sub url
return '#/list';
}
if (!id && tail) {
// unsaved dashboard, but probably state in URL
return `#/create${tail || ''}`;
}
// persisted dashboard, probably with url state
return `#/view/${id}${tail || ''}`;
}
);
kibanaLegacy.forwardApp(
DashboardConstants.DASHBOARDS_ID,
DashboardConstants.DASHBOARDS_ID,
path => {
const [, tail] = /(\?.*)/.exec(path) || [];
// carry over query if it exists
return `#/list${tail || ''}`;
}
);
if (home) {
home.featureCatalogue.register({
@ -297,13 +329,30 @@ export class DashboardPlugin
defaultMessage: 'Display and share a collection of visualizations and saved searches.',
}),
icon: 'dashboardApp',
path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`,
path: `/app/dashboards#${DashboardConstants.LANDING_PAGE_PATH}`,
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA,
});
}
}
private addEmbeddableToDashboard(
core: CoreStart,
{ embeddableId, embeddableType }: { embeddableId: string; embeddableType: string }
) {
if (!this.getActiveUrl) {
throw new Error('dashboard is not ready yet.');
}
const lastDashboardUrl = this.getActiveUrl();
const dashboardUrl = addEmbeddableToDashboardUrl(
lastDashboardUrl,
embeddableId,
embeddableType
);
core.application.navigateToApp('dashboards', { path: dashboardUrl });
}
public start(core: CoreStart, plugins: StartDependencies): DashboardStart {
const { notifications } = core;
const {
@ -335,6 +384,7 @@ export class DashboardPlugin
});
return {
getSavedDashboardLoader: () => savedDashboardLoader,
addEmbeddableToDashboard: this.addEmbeddableToDashboard.bind(this, core),
dashboardUrlGenerator: this.dashboardUrlGenerator,
};
}

View file

@ -106,7 +106,7 @@ export function createSavedDashboardClass(
refreshInterval: undefined,
},
});
this.getFullPath = () => `/app/kibana#${createDashboardEditUrl(String(this.id))}`;
this.getFullPath = () => `/app/dashboards#${createDashboardEditUrl(String(this.id))}`;
}
getQuery() {

View file

@ -24,7 +24,7 @@ import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store
import { esFilters, Filter } from '../../data/public';
import { SavedObjectLoader } from '../../saved_objects/public';
const APP_BASE_PATH: string = 'xyz/app/kibana';
const APP_BASE_PATH: string = 'xyz/app/dashboards';
const createMockDashboardLoader = (
dashboardToFilters: {
@ -63,7 +63,7 @@ describe('dashboard url generator', () => {
})
);
const url = await generator.createUrl!({});
expect(url).toMatchInlineSnapshot(`"xyz/app/kibana#/dashboard?_a=()&_g=()"`);
expect(url).toMatchInlineSnapshot(`"xyz/app/dashboards#/create?_a=()&_g=()"`);
});
test('creates a link with global time range set up', async () => {
@ -78,7 +78,7 @@ describe('dashboard url generator', () => {
timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
});
expect(url).toMatchInlineSnapshot(
`"xyz/app/kibana#/dashboard?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))"`
`"xyz/app/dashboards#/create?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))"`
);
});
@ -118,7 +118,7 @@ describe('dashboard url generator', () => {
query: { query: 'bye', language: 'kuery' },
});
expect(url).toMatchInlineSnapshot(
`"xyz/app/kibana#/dashboard/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))"`
`"xyz/app/dashboards#/view/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))"`
);
});
@ -265,7 +265,7 @@ describe('dashboard url generator', () => {
expect(url).not.toEqual(expect.stringContaining('query:savedfilter1'));
expect(url).not.toEqual(expect.stringContaining('query:appliedfilter'));
expect(url).toMatchInlineSnapshot(
`"xyz/app/kibana#/dashboard/dashboard1?_a=(filters:!())&_g=(filters:!())"`
`"xyz/app/dashboards#/view/dashboard1?_a=(filters:!())&_g=(filters:!())"`
);
});
@ -284,7 +284,7 @@ describe('dashboard url generator', () => {
dashboardId: 'dashboard1',
});
expect(url).not.toEqual(expect.stringContaining('filters'));
expect(url).toMatchInlineSnapshot(`"xyz/app/kibana#/dashboard/dashboard1?_a=()&_g=()"`);
expect(url).toMatchInlineSnapshot(`"xyz/app/dashboards#/view/dashboard1?_a=()&_g=()"`);
});
test('can turn off preserving filters', async () => {

View file

@ -87,7 +87,7 @@ export const createDashboardUrlGenerator = (
const startServices = await getStartServices();
const useHash = state.useHash ?? startServices.useHashedUrl;
const appBasePath = startServices.appBasePath;
const hash = state.dashboardId ? `dashboard/${state.dashboardId}` : `dashboard`;
const hash = state.dashboardId ? `view/${state.dashboardId}` : `create`;
const getSavedFiltersFromDestinationDashboardIfNeeded = async (): Promise<Filter[]> => {
if (state.preserveSavedFilters === false) return [];

View file

@ -23,17 +23,17 @@ describe('', () => {
it('addEmbeddableToDashboardUrl when dashboard is not saved', () => {
const id = '123eb456cd';
const url =
"/pep/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
expect(addEmbeddableToDashboardUrl(url, id)).toEqual(
`/dashboard?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization`
"/pep/app/dashboards#/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
expect(addEmbeddableToDashboardUrl(url, id, 'visualization')).toEqual(
`/pep/app/dashboards#/create?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization`
);
});
it('addEmbeddableToDashboardUrl when dashboard is saved', () => {
const id = '123eb456cd';
const url =
"/pep/app/kibana#/dashboard/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
expect(addEmbeddableToDashboardUrl(url, id)).toEqual(
`/dashboard/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization`
"/pep/app/dashboards#/view/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(description:'',filters:!())";
expect(addEmbeddableToDashboardUrl(url, id, 'visualization')).toEqual(
`/pep/app/dashboards#/view/9b780cd0-3dd3-11e8-b2b9-5d5dc1715159?_a=%28description%3A%27%27%2Cfilters%3A%21%28%29%29&_g=%28refreshInterval%3A%28pause%3A%21t%2Cvalue%3A0%29%2Ctime%3A%28from%3Anow-15m%2Cto%3Anow%29%29&addEmbeddableId=${id}&addEmbeddableType=visualization`
);
});
});

View file

@ -18,25 +18,28 @@
*/
import { parseUrl, stringify } from 'query-string';
import { DashboardConstants } from '../../../../../dashboard/public';
import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../visualizations/public';
import { DashboardConstants } from '../index';
/** *
* Returns relative dashboard URL with added embeddableType and embeddableId query params
* eg.
* input: url: lol/app/kibana#/dashboard?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)), embeddableId: 12345
* output: /dashboard?addEmbeddableType=visualization&addEmbeddableId=12345&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))
* @param url dasbhoard absolute url
* @param embeddableId id of the saved visualization
* input: url: #/create?_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now)), embeddableId: 12345
* output: #/create?addEmbeddableType=visualization&addEmbeddableId=12345&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))
* @param url dasbhoard hash part of the url
* @param embeddableId id of the saved embeddable
* @param embeddableType type of the embeddable
*/
export function addEmbeddableToDashboardUrl(dashboardUrl: string, embeddableId: string) {
export function addEmbeddableToDashboardUrl(
dashboardUrl: string,
embeddableId: string,
embeddableType: string
) {
const { url, query } = parseUrl(dashboardUrl);
const [, dashboardId] = url.split(DashboardConstants.CREATE_NEW_DASHBOARD_URL);
if (embeddableId) {
query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = VISUALIZE_EMBEDDABLE_TYPE;
query[DashboardConstants.ADD_EMBEDDABLE_TYPE] = embeddableType;
query[DashboardConstants.ADD_EMBEDDABLE_ID] = embeddableId;
}
return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}${dashboardId}?${stringify(query)}`;
return `${url}?${stringify(query)}`;
}

View file

@ -36,7 +36,7 @@ export const dashboardSavedObjectType: SavedObjectsType = {
},
getInAppUrl(obj) {
return {
path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`,
path: `/app/dashboards#/view/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'dashboard.show',
};
},

View file

@ -246,18 +246,18 @@ describe('UrlFormat', () => {
test('should support multiple types of relative urls', () => {
const parsedUrl = {
origin: 'http://kibana.host.com',
pathname: '/nbc/app/kibana#/discover',
pathname: '/nbc/app/discover#/',
basePath: '/nbc',
};
const url = new UrlFormat({ parsedUrl });
const converter = url.getConverterFor(HTML_CONTEXT_TYPE) as Function;
expect(converter('#/foo')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/kibana#/discover#/foo" target="_blank" rel="noopener noreferrer">#/foo</a></span>'
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/discover#/#/foo" target="_blank" rel="noopener noreferrer">#/foo</a></span>'
);
expect(converter('/nbc/app/kibana#/discover')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/kibana#/discover" target="_blank" rel="noopener noreferrer">/nbc/app/kibana#/discover</a></span>'
expect(converter('/nbc/app/discover#/')).toBe(
'<span ng-non-bindable><a href="http://kibana.host.com/nbc/app/discover#/" target="_blank" rel="noopener noreferrer">/nbc/app/discover#/</a></span>'
);
expect(converter('../foo/bar')).toBe(

View file

@ -61,26 +61,25 @@ export const createEnsureDefaultIndexPattern = (core: CoreStart) => {
core.uiSettings.set('defaultIndex', defaultId);
} else {
const canManageIndexPatterns = core.application.capabilities.management.kibana.index_patterns;
const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home';
const redirectTarget = canManageIndexPatterns ? 'management' : 'home';
if (timeoutId) {
clearTimeout(timeoutId);
}
const bannerMessage = i18n.translate(
'data.indexPatterns.ensureDefaultIndexPattern.bannerLabel',
{
defaultMessage:
"In order to visualize and explore data in Kibana, you'll need to create an index pattern to retrieve data from Elasticsearch.",
}
);
// Avoid being hostile to new users who don't have an index pattern setup yet
// give them a friendly info message instead of a terse error message
bannerId = core.overlays.banners.replace(
bannerId,
toMountPoint(
<EuiCallOut
color="warning"
iconType="iInCircle"
title={i18n.translate('data.indexPatterns.ensureDefaultIndexPattern.bannerLabel', {
defaultMessage:
"In order to visualize and explore data in Kibana, you'll need to create an index pattern to retrieve data from Elasticsearch.",
})}
/>
)
toMountPoint(<EuiCallOut color="warning" iconType="iInCircle" title={bannerMessage} />)
);
// hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around
@ -89,7 +88,13 @@ export const createEnsureDefaultIndexPattern = (core: CoreStart) => {
timeoutId = undefined;
}, 15000);
history.push(redirectTarget);
if (redirectTarget === 'home') {
core.application.navigateToApp('home');
} else {
window.location.href = core.http.basePath.prepend(
`/app/kibana#/management/kibana/index_pattern?bannerMessage=${bannerMessage}`
);
}
// return never-resolving promise to stop resolving and wait for the url change
return new Promise(() => {});

View file

@ -32,7 +32,7 @@ export const querySavedObjectType: SavedObjectsType = {
},
getInAppUrl(obj) {
return {
path: `/app/kibana#/discover?_a=(savedQuery:'${encodeURIComponent(obj.id)}')`,
path: `/app/discover#/?_a=(savedQuery:'${encodeURIComponent(obj.id)}')`,
uiCapabilitiesPath: 'discover.show',
};
},

View file

@ -36,7 +36,7 @@ export const searchSavedObjectType: SavedObjectsType = {
},
getInAppUrl(obj) {
return {
path: `/app/kibana#/discover/${encodeURIComponent(obj.id)}`,
path: `/app/discover#/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'discover.show',
};
},

View file

@ -2,7 +2,7 @@
The ui/registry/dev_tools is removed in favor of the `devTools` plugin which exposes a register method in the setup contract.
Registering app works mostly the same as registering apps in core.application.register.
Routing will be handled by the id of the dev tool - your dev tool will be mounted when the URL matches `/app/kibana#/dev_tools/<YOUR ID>`.
Routing will be handled by the id of the dev tool - your dev tool will be mounted when the URL matches `/app/dev_tools#/<YOUR ID>`.
This API doesn't support angular, for registering angular dev tools, bootstrap a local module on mount into the given HTML element.
During the migration this plugin exposes the registered dev tools in the start contract. This is necessary to keep the dev tools app

View file

@ -24,7 +24,7 @@ import * as React from 'react';
import ReactDOM from 'react-dom';
import { useEffect, useRef } from 'react';
import { AppMountContext, AppMountDeprecated } from 'kibana/public';
import { AppMountContext, AppMountDeprecated, ScopedHistory } from 'kibana/public';
import { DevToolApp } from './dev_tool';
interface DevToolsWrapperProps {
@ -67,7 +67,7 @@ function DevToolsWrapper({
isSelected={currentDevTool === activeDevTool}
onClick={() => {
if (!currentDevTool.isDisabled()) {
updateRoute(`/dev_tools/${currentDevTool.id}`);
updateRoute(`/${currentDevTool.id}`);
}
}}
>
@ -114,7 +114,7 @@ function DevToolsWrapper({
function redirectOnMissingCapabilities(appMountContext: AppMountContext) {
if (!appMountContext.core.application.capabilities.dev_tools.show) {
window.location.hash = '/home';
appMountContext.core.application.navigateToApp('home');
return true;
}
return false;
@ -135,13 +135,21 @@ function setBadge(appMountContext: AppMountContext) {
});
}
function setTitle(appMountContext: AppMountContext) {
appMountContext.core.chrome.docTitle.change(
i18n.translate('devTools.pageTitle', {
defaultMessage: 'Dev Tools',
})
);
}
function setBreadcrumbs(appMountContext: AppMountContext) {
appMountContext.core.chrome.setBreadcrumbs([
{
text: i18n.translate('devTools.k7BreadcrumbsDevToolsLabel', {
defaultMessage: 'Dev Tools',
}),
href: '#/dev_tools',
href: '#/',
},
]);
}
@ -149,7 +157,7 @@ function setBreadcrumbs(appMountContext: AppMountContext) {
export function renderApp(
element: HTMLElement,
appMountContext: AppMountContext,
basePath: string,
history: ScopedHistory,
devTools: readonly DevToolApp[]
) {
if (redirectOnMissingCapabilities(appMountContext)) {
@ -157,6 +165,7 @@ export function renderApp(
}
setBadge(appMountContext);
setBreadcrumbs(appMountContext);
setTitle(appMountContext);
ReactDOM.render(
<I18nProvider>
<Router>
@ -167,7 +176,7 @@ export function renderApp(
.map(devTool => (
<Route
key={devTool.id}
path={`/dev_tools/${devTool.id}`}
path={`/${devTool.id}`}
exact={!devTool.enableRouting}
render={props => (
<DevToolsWrapper
@ -179,8 +188,8 @@ export function renderApp(
)}
/>
))}
<Route path="/dev_tools">
<Redirect to={`/dev_tools/${devTools[0].id}`} />
<Route path="/">
<Redirect to={`/${devTools[0].id}`} />
</Route>
</Switch>
</Router>
@ -188,7 +197,16 @@ export function renderApp(
element
);
return () => ReactDOM.unmountComponentAtNode(element);
// dispatch synthetic hash change event to update hash history objects
// this is necessary because hash updates triggered by using popState won't trigger this event naturally.
const unlisten = history.listen(() => {
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
return () => {
ReactDOM.unmountComponentAtNode(element);
unlisten();
};
}
function isAppMountDeprecated(mount: (...args: any[]) => any): mount is AppMountDeprecated {

View file

@ -19,3 +19,9 @@
.devApp {
height: 100%;
}
.devAppWrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
}

View file

@ -17,10 +17,13 @@
* under the License.
*/
import { CoreSetup, Plugin } from 'kibana/public';
import { BehaviorSubject } from 'rxjs';
import { AppUpdater, CoreSetup, Plugin } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { sortBy } from 'lodash';
import { KibanaLegacySetup } from '../../kibana_legacy/public';
import { CreateDevToolArgs, DevToolApp, createDevToolApp } from './dev_tool';
import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public';
import './index.scss';
@ -38,42 +41,34 @@ export interface DevToolsSetup {
register: (devTool: CreateDevToolArgs) => DevToolApp;
}
export interface DevToolsStart {
/**
* Returns all registered dev tools in an ordered array.
* This function is only exposed because the dev tools app
* actually rendering the tool has to stay in the legacy platform
* for now. Once it is moved into this plugin, this function
* becomes an implementation detail.
* @deprecated
*/
getSortedDevTools: () => readonly DevToolApp[];
}
export class DevToolsPlugin implements Plugin<DevToolsSetup, DevToolsStart> {
export class DevToolsPlugin implements Plugin<DevToolsSetup, void> {
private readonly devTools = new Map<string, DevToolApp>();
private appStateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
private getSortedDevTools(): readonly DevToolApp[] {
return sortBy([...this.devTools.values()], 'order');
}
public setup(core: CoreSetup, { kibanaLegacy }: { kibanaLegacy: KibanaLegacySetup }) {
kibanaLegacy.registerLegacyApp({
core.application.register({
id: 'dev_tools',
title: 'Dev Tools',
title: i18n.translate('devTools.devToolsTitle', {
defaultMessage: 'Dev Tools',
}),
updater$: this.appStateUpdater,
euiIconType: 'devToolsApp',
order: 9001,
category: DEFAULT_APP_CATEGORIES.management,
mount: async (appMountContext, params) => {
if (!this.getSortedDevTools) {
throw new Error('not started yet');
}
const { renderApp } = await import('./application');
return renderApp(
params.element,
appMountContext,
params.appBasePath,
this.getSortedDevTools()
);
params.element.classList.add('devAppWrapper');
return renderApp(params.element, appMountContext, params.history, this.getSortedDevTools());
},
});
kibanaLegacy.forwardApp('dev_tools', 'dev_tools');
return {
register: (devToolArgs: CreateDevToolArgs) => {
@ -91,9 +86,9 @@ export class DevToolsPlugin implements Plugin<DevToolsSetup, DevToolsStart> {
}
public start() {
return {
getSortedDevTools: this.getSortedDevTools.bind(this),
};
if (this.getSortedDevTools().length === 0) {
this.appStateUpdater.next(() => ({ navLinkStatus: AppNavLinkStatus.hidden }));
}
}
public stop() {}

View file

@ -1,3 +1,10 @@
.dscAppWrapper {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
discover-app {
flex-grow: 1;
}

View file

@ -1,7 +1,6 @@
<context-app
anchor-id="contextAppRoute.anchorId"
columns="contextAppRoute.state.columns"
discover-url="contextAppRoute.discoverUrl"
index-pattern="contextAppRoute.indexPattern"
filters="contextAppRoute.filters"
predecessor-count="contextAppRoute.state.predecessorCount"

View file

@ -47,10 +47,10 @@ getAngularModule().config($routeProvider => {
$routeProvider
// deprecated route, kept for compatibility
// should be removed in the future
.when('/discover/context/:indexPatternId/:type/:id*', {
redirectTo: '/discover/context/:indexPatternId/:id',
.when('/context/:indexPatternId/:type/:id*', {
redirectTo: '/context/:indexPatternId/:id',
})
.when('/discover/context/:indexPatternId/:id*', {
.when('/context/:indexPatternId/:id*', {
controller: ContextAppRouteController,
k7Breadcrumbs,
controllerAs: 'contextAppRoute',
@ -86,7 +86,6 @@ function ContextAppRouteController($routeParams, $scope, $route) {
this.state = { ...appState.getState() };
this.anchorId = $routeParams.id;
this.indexPattern = indexPattern;
this.discoverUrl = getServices().chrome.navLinks.get('kibana:discover').url;
filterManager.setFilters(_.cloneDeep(getFilters()));
startStateSync();

View file

@ -44,20 +44,8 @@
>
<span
i18n-id="discover.context.reloadPageDescription.reloadOrVisitTextMessage"
i18n-default-message="Please reload or visit"
i18n-description="Part of composite text discover.context.reloadPageDescription.reloadOrVisitTextMessage + discover.context.reloadPageDescription.discoverLinkText + discover.context.reloadPageDescription.selectValidAnchorDocumentTextMessage"
i18n-default-message="Please reload or go back to the document list to select a valid anchor document."
></span>
<a
ng-href="{{ contextApp.state.navigation.discover.url }}"
i18n-id="discover.context.reloadPageDescription.discoverLinkText"
i18n-default-message="Discover"
i18n-description="Part of composite text discover.context.reloadPageDescription.reloadOrVisitTextMessage + discover.context.reloadPageDescription.discoverLinkText + discover.context.reloadPageDescription.selectValidAnchorDocumentTextMessage"
></a>
<span
i18n-id="discover.context.reloadPageDescription.selectValidAnchorDocumentTextMessage"
i18n-default-message="to select a valid anchor document."
i18n-description="Part of composite text discover.context.reloadPageDescription.reloadOrVisitTextMessage + discover.context.reloadPageDescription.discoverLinkText + discover.context.reloadPageDescription.selectValidAnchorDocumentTextMessage"
></span>
</div>
</div>
</div>

View file

@ -51,7 +51,6 @@ module.directive('contextApp', function ContextApp() {
predecessorCount: '=',
successorCount: '=',
sort: '=',
discoverUrl: '=',
},
template: contextAppTemplate,
};
@ -63,8 +62,7 @@ function ContextAppController($scope, Private) {
const queryActions = Private(QueryActionsProvider);
this.state = createInitialState(
parseInt(uiSettings.get('context:step'), 10),
getFirstSortableField(this.indexPattern, uiSettings.get('context:tieBreakerFields')),
this.discoverUrl
getFirstSortableField(this.indexPattern, uiSettings.get('context:tieBreakerFields'))
);
this.actions = _.mapValues(
@ -129,7 +127,7 @@ function ContextAppController($scope, Private) {
);
}
function createInitialState(defaultStepSize, tieBreakerField, discoverUrl) {
function createInitialState(defaultStepSize, tieBreakerField) {
return {
queryParameters: createInitialQueryParametersState(defaultStepSize, tieBreakerField),
rows: {
@ -139,10 +137,5 @@ function createInitialState(defaultStepSize, tieBreakerField, discoverUrl) {
successors: [],
},
loadingStatus: createInitialLoadingStatusState(),
navigation: {
discover: {
url: discoverUrl,
},
},
};
}

View file

@ -106,7 +106,7 @@ app.config($routeProvider => {
};
},
};
$routeProvider.when('/discover/:id?', {
$routeProvider.when('/:id?', {
...defaults,
template: indexTemplate,
reloadOnSearch: false,
@ -151,14 +151,17 @@ app.config($routeProvider => {
.catch(
redirectWhenMissing({
history,
navigateToApp: core.application.navigateToApp,
mapping: {
search: '/discover',
'index-pattern':
'/management/kibana/objects/savedSearches/' + $route.current.params.id,
search: '/',
'index-pattern': {
app: 'kibana',
path: `#/management/kibana/objects/savedSearches/${$route.current.params.id}`,
},
},
toastNotifications,
onBeforeRedirect() {
getUrlTracker().setTrackedUrl('/discover');
getUrlTracker().setTrackedUrl('/');
},
})
),
@ -259,11 +262,11 @@ function discoverController(
}
});
// this listener is waiting for such a path http://localhost:5601/app/kibana#/discover
// this listener is waiting for such a path http://localhost:5601/app/discover#/
// which could be set through pressing "New" button in top nav or go to "Discover" plugin from the sidebar
// to reload the page in a right way
const unlistenHistoryBasePath = history.listen(({ pathname, search, hash }) => {
if (!search && !hash && pathname === '/discover') {
if (!search && !hash && pathname === '/') {
$route.reload();
}
});
@ -338,7 +341,7 @@ function discoverController(
}),
run: function() {
$scope.$evalAsync(() => {
history.push('/discover');
history.push('/');
});
},
testId: 'discoverNewButton',
@ -408,7 +411,7 @@ function discoverController(
testId: 'discoverOpenButton',
run: () => {
showOpenSearchPanel({
makeUrl: searchId => `#/discover/${encodeURIComponent(searchId)}`,
makeUrl: searchId => `#/${encodeURIComponent(searchId)}`,
I18nContext: core.i18n.Context,
});
},
@ -497,7 +500,7 @@ function discoverController(
chrome.setBreadcrumbs([
{
text: discoverBreadcrumbsTitle,
href: '#/discover',
href: '#/',
},
{ text: savedSearch.title },
]);
@ -755,7 +758,7 @@ function discoverController(
});
if (savedSearch.id !== $route.current.params.id) {
history.push(`/discover/${encodeURIComponent(savedSearch.id)}`);
history.push(`/${encodeURIComponent(savedSearch.id)}`);
} else {
// Update defaults so that "reload saved query" functions correctly
setAppState(getStateDefaults());
@ -926,11 +929,11 @@ function discoverController(
};
$scope.resetQuery = function() {
history.push(`/discover/${encodeURIComponent($route.current.params.id)}`);
history.push(`/${encodeURIComponent($route.current.params.id)}`);
};
$scope.newQuery = function() {
history.push('/discover');
history.push('/');
};
$scope.updateDataSource = () => {

View file

@ -44,11 +44,11 @@ app.directive('discoverDoc', function(reactDirective: any) {
app.config(($routeProvider: any) => {
$routeProvider
.when('/discover/doc/:indexPattern/:index/:type', {
redirectTo: '/discover/doc/:indexPattern/:index',
.when('/doc/:indexPattern/:index/:type', {
redirectTo: '/doc/:indexPattern/:index',
})
// the new route, es 7 deprecated types, es 8 removed them
.when('/discover/doc/:indexPattern/:index', {
.when('/doc/:indexPattern/:index', {
// have to be written as function expression, because it's not compiled in dev mode
// eslint-disable-next-line object-shorthand
controller: function($scope: LazyScope, $route: any, es: any) {

View file

@ -106,9 +106,9 @@ export function createTableRowDirective($compile: ng.ICompileService, $httpParam
};
$scope.getContextAppHref = () => {
const path = `#/discover/context/${encodeURIComponent(
$scope.indexPattern.id
)}/${encodeURIComponent($scope.row._id)}`;
const path = `#/context/${encodeURIComponent($scope.indexPattern.id)}/${encodeURIComponent(
$scope.row._id
)}`;
const globalFilters: any = getServices().filterManager.getGlobalFilters();
const appFilters: any = getServices().filterManager.getAppFilters();
const hash = $httpParamSerializer({

View file

@ -30,7 +30,7 @@
<a
class="euiLink"
data-test-subj="docTableRowAction"
ng-href="#/discover/doc/{{indexPattern.id}}/{{row._index}}?id={{uriEncodedId}}"
ng-href="#/doc/{{indexPattern.id}}/{{row._index}}?id={{uriEncodedId}}"
i18n-id="discover.docTable.tableRow.viewSingleDocumentLinkText"
i18n-default-message="View single document"
></a>

View file

@ -16,6 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
// required for i18nIdDirective
import 'angular-sanitize';
// required for ngRoute
import 'angular-route';
import './discover';
import './doc';
import './context';

View file

@ -22,6 +22,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { DiscoverFieldBucket } from './discover_field_bucket';
import { getWarnings } from './lib/get_warnings';
import { Bucket, FieldDetails } from './types';
import { getServices } from '../../../kibana_services';
import { IndexPatternField, IndexPattern } from '../../../../../data/public';
interface DiscoverFieldDetailsProps {
@ -79,7 +80,11 @@ export function DiscoverFieldDetails({
<>
<EuiSpacer size={'s'} />
<EuiLink
href={details.visualizeUrl}
onClick={() => {
getServices().core.application.navigateToApp(details.visualizeUrl.app, {
path: details.visualizeUrl.path,
});
}}
className="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
data-test-subj={`fieldVisualize-${field.name}`}
>

View file

@ -29,13 +29,6 @@ import { AppState } from '../../../angular/discover_state';
import { DiscoverServices } from '../../../../build_services';
import { VisualizationsStart, VisTypeAlias } from '../../../../../../visualizations/public';
function getMapsAppBaseUrl(visualizations: VisualizationsStart) {
const mapsAppVisAlias = visualizations.getAliases().find(({ name }) => {
return name === 'maps';
});
return mapsAppVisAlias ? mapsAppVisAlias.aliasUrl : null;
}
export function isMapsAppRegistered(visualizations: VisualizationsStart) {
return visualizations.getAliases().some(({ name }: VisTypeAlias) => {
return name === 'maps';
@ -60,13 +53,12 @@ export function getMapsAppUrl(
field: IFieldType,
indexPattern: IIndexPattern,
appState: AppState,
columns: string[],
services: DiscoverServices
columns: string[]
) {
const mapAppParams = new URLSearchParams();
// Copy global state
const locationSplit = window.location.href.split('discover?');
const locationSplit = window.location.hash.split('?');
if (locationSplit.length > 1) {
const discoverParams = new URLSearchParams(locationSplit[1]);
const globalStateUrlValue = discoverParams.get('_g');
@ -109,9 +101,10 @@ export function getMapsAppUrl(
])
);
return services.addBasePath(
`${getMapsAppBaseUrl(services.visualizations)}?${mapAppParams.toString()}`
);
return {
app: 'maps',
path: `#/map?${mapAppParams.toString()}`,
};
}
export function getVisualizeUrl(
@ -128,7 +121,7 @@ export function getVisualizeUrl(
(field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
isMapsAppRegistered(services.visualizations)
) {
return getMapsAppUrl(field, indexPattern, state, columns, services);
return getMapsAppUrl(field, indexPattern, state, columns);
}
let agg;
@ -181,5 +174,8 @@ export function getVisualizeUrl(
},
};
return `#/visualize/create?${stringify(linkUrlParams)}`;
return {
app: 'visualize',
path: `#/create?${stringify(linkUrlParams)}`,
};
}

View file

@ -27,7 +27,10 @@ export interface FieldDetails {
exists: number;
total: boolean;
buckets: Bucket[];
visualizeUrl: string;
visualizeUrl: {
app: string;
path: string;
};
}
export interface Bucket {

View file

@ -71,6 +71,7 @@ interface SearchEmbeddableConfig {
$compile: ng.ICompileService;
savedSearch: SavedSearch;
editUrl: string;
editPath: string;
indexPatterns?: IndexPattern[];
editable: boolean;
filterManager: FilterManager;
@ -102,6 +103,7 @@ export class SearchEmbeddable extends Embeddable<SearchInput, SearchOutput>
$compile,
savedSearch,
editUrl,
editPath,
indexPatterns,
editable,
filterManager,
@ -112,7 +114,14 @@ export class SearchEmbeddable extends Embeddable<SearchInput, SearchOutput>
) {
super(
initialInput,
{ defaultTitle: savedSearch.title, editUrl, indexPatterns, editable },
{
defaultTitle: savedSearch.title,
editUrl,
editPath,
editApp: 'discover',
indexPatterns,
editable,
},
parent
);
@ -338,6 +347,9 @@ export class SearchEmbeddable extends Embeddable<SearchInput, SearchOutput>
this.prevFilters = this.input.filters;
this.prevQuery = this.input.query;
this.prevTimeRange = this.input.timeRange;
} else if (this.searchScope) {
// trigger a digest cycle to make sure non-fetch relevant changes are propagated
this.searchScope.$applyAsync();
}
}
}

View file

@ -87,7 +87,7 @@ export class SearchEmbeddableFactory
const filterManager = getServices().filterManager;
const url = await getServices().getSavedSearchUrlById(savedObjectId);
const editUrl = getServices().addBasePath(`/app/kibana${url}`);
const editUrl = getServices().addBasePath(`/app/discover${url}`);
try {
const savedObject = await getServices().getSavedSearchById(savedObjectId);
const indexPattern = savedObject.searchSource.getField('index');
@ -98,6 +98,7 @@ export class SearchEmbeddableFactory
$rootScope,
$compile,
editUrl,
editPath: url,
filterManager,
editable: getServices().capabilities.discover.save as boolean,
indexPatterns: indexPattern ? [indexPattern] : [],

View file

@ -25,7 +25,7 @@ export function getRootBreadcrumbs() {
text: i18n.translate('discover.rootBreadcrumb', {
defaultMessage: 'Discover',
}),
href: '#/discover',
href: '#/',
},
];
}

View file

@ -20,6 +20,7 @@
// inner angular imports
// these are necessary to bootstrap the local angular.
// They can stay even after NP cutover
import './application/index.scss';
import angular from 'angular';
// required for `ngSanitize` angular module
import 'angular-sanitize';
@ -59,6 +60,7 @@ import {
import { createDiscoverSidebarDirective } from './application/components/sidebar';
import { createHitsCounterDirective } from '././application/components/hits_counter';
import { DiscoverStartPlugins } from './plugin';
import { getScopedHistory } from './kibana_services';
/**
* returns the main inner angular module, it contains all the parts of Angular Discover
@ -72,7 +74,7 @@ export function getInnerAngularModule(
) {
initAngularBootstrap();
const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data);
configureAppAngularModule(module, { core, env: context.env }, true);
configureAppAngularModule(module, { core, env: context.env }, true, getScopedHistory);
return module;
}
@ -86,7 +88,6 @@ export function getInnerAngularModuleEmbeddable(
context: PluginInitializerContext
) {
const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data, true);
configureAppAngularModule(module, { core, env: context.env }, true);
return module;
}

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { createHashHistory } from 'history';
import { ScopedHistory } from 'kibana/public';
import { DiscoverServices } from './build_services';
import { createGetterSetter } from '../../kibana_utils/public';
import { search } from '../../data/public';
@ -57,12 +58,15 @@ export const [getUrlTracker, setUrlTracker] = createGetterSetter<{
export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter<DocViewsRegistry>(
'DocViewsRegistry'
);
/**
* Makes sure discover and context are using one instance of history
*/
export const getHistory = _.once(() => createHashHistory());
export const [getScopedHistory, setScopedHistory] = createGetterSetter<ScopedHistory>(
'scopedHistory'
);
export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search;
export { unhashUrl, redirectWhenMissing } from '../../kibana_utils/public';
export { formatMsg, formatStack, subscribeWithScope } from '../../kibana_legacy/public';

View file

@ -24,6 +24,7 @@ import { filter, map } from 'rxjs/operators';
import {
AppMountParameters,
AppUpdater,
CoreSetup,
CoreStart,
Plugin,
@ -35,26 +36,27 @@ import { ChartsPluginStart } from 'src/plugins/charts/public';
import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public';
import { SharePluginStart } from 'src/plugins/share/public';
import { VisualizationsStart, VisualizationsSetup } from 'src/plugins/visualizations/public';
import { KibanaLegacySetup, AngularRenderedAppUpdater } from 'src/plugins/kibana_legacy/public';
import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public';
import { HomePublicPluginSetup } from 'src/plugins/home/public';
import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public';
import { DataPublicPluginStart, DataPublicPluginSetup, esFilters } from '../../data/public';
import { SavedObjectLoader } from '../../saved_objects/public';
import { createKbnUrlTracker } from '../../kibana_utils/public';
import { DEFAULT_APP_CATEGORIES } from '../../../core/public';
import { DocViewInput, DocViewInputFn } from './application/doc_views/doc_views_types';
import { DocViewsRegistry } from './application/doc_views/doc_views_registry';
import { DocViewTable } from './application/components/table/table';
import { JsonCodeBlock } from './application/components/json_code_block/json_code_block';
import {
getHistory,
setDocViewsRegistry,
setUrlTracker,
setAngularModule,
setServices,
setScopedHistory,
getScopedHistory,
} from './kibana_services';
import { createSavedSearchesLoader } from './saved_searches';
import { getInnerAngularModuleEmbeddable, getInnerAngularModule } from './get_inner_angular';
import { registerFeature } from './register_feature';
import { buildServices } from './build_services';
@ -114,7 +116,7 @@ export class DiscoverPlugin
implements Plugin<DiscoverSetup, DiscoverStart, DiscoverSetupPlugins, DiscoverStartPlugins> {
constructor(private readonly initializerContext: PluginInitializerContext) {}
private appStateUpdater = new BehaviorSubject<AngularRenderedAppUpdater>(() => ({}));
private appStateUpdater = new BehaviorSubject<AppUpdater>(() => ({}));
private docViewsRegistry: DocViewsRegistry | null = null;
private embeddableInjector: auto.IInjectorService | null = null;
private stopUrlTracking: (() => void) | undefined = undefined;
@ -155,9 +157,9 @@ export class DiscoverPlugin
// we pass getter here instead of plain `history`,
// so history is lazily created (when app is mounted)
// this prevents redundant `#` when not in discover app
getHistory,
baseUrl: core.http.basePath.prepend('/app/kibana'),
defaultSubUrl: '#/discover',
getHistory: getScopedHistory,
baseUrl: core.http.basePath.prepend('/app/discover'),
defaultSubUrl: '#/',
storageKey: `lastUrl:${core.http.basePath.get()}:discover`,
navLinkUpdater$: this.appStateUpdater,
toastNotifications: core.notifications.toasts,
@ -182,13 +184,14 @@ export class DiscoverPlugin
};
this.docViewsRegistry.setAngularInjectorGetter(this.getEmbeddableInjector);
plugins.kibanaLegacy.registerLegacyApp({
core.application.register({
id: 'discover',
title: 'Discover',
updater$: this.appStateUpdater.asObservable(),
navLinkId: 'kibana:discover',
order: -1004,
euiIconType: 'discoverApp',
defaultPath: '#/',
category: DEFAULT_APP_CATEGORIES.kibana,
mount: async (params: AppMountParameters) => {
if (!this.initializeServices) {
throw Error('Discover plugin method initializeServices is undefined');
@ -196,6 +199,7 @@ export class DiscoverPlugin
if (!this.initializeInnerAngular) {
throw Error('Discover plugin method initializeInnerAngular is undefined');
}
setScopedHistory(params.history);
appMounted();
const {
plugins: { data: dataStart },
@ -205,6 +209,7 @@ export class DiscoverPlugin
// make sure the index pattern list is up to date
await dataStart.indexPatterns.clearCache();
const { renderApp } = await import('./application/application');
params.element.classList.add('dscAppWrapper');
const unmount = await renderApp(innerAngularName, params.element);
return () => {
unmount();
@ -213,6 +218,8 @@ export class DiscoverPlugin
},
});
plugins.kibanaLegacy.forwardApp('discover', 'discover');
if (plugins.home) {
registerFeature(plugins.home);
}
@ -236,6 +243,7 @@ export class DiscoverPlugin
return;
}
// this is used by application mount and tests
const { getInnerAngularModule } = await import('./get_inner_angular');
const module = getInnerAngularModule(
innerAngularName,
core,
@ -305,6 +313,7 @@ export class DiscoverPlugin
throw Error('Discover plugin getEmbeddableInjector: initializeServices is undefined');
}
const { core, plugins } = await this.initializeServices();
const { getInnerAngularModuleEmbeddable } = await import('./get_inner_angular');
getInnerAngularModuleEmbeddable(
embeddableAngularName,
core,

View file

@ -29,7 +29,7 @@ export function registerFeature(home: HomePublicPluginSetup) {
defaultMessage: 'Interactively explore your data by querying and filtering raw documents.',
}),
icon: 'discoverApp',
path: '/app/kibana#/discover',
path: '/app/discover#/',
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA,
});

View file

@ -66,7 +66,7 @@ export function createSavedSearchClass(services: SavedObjectKibanaServices) {
});
this.showInRecentlyAccessed = true;
this.id = id;
this.getFullPath = () => `/app/kibana#/discover/${String(id)}`;
this.getFullPath = () => `/app/discover#/${String(id)}`;
}
}

View file

@ -34,7 +34,7 @@ export function createSavedSearchesLoader(services: SavedObjectKibanaServices) {
nouns: 'saved searches',
};
savedSearchLoader.urlFor = (id: string) => `#/discover/${encodeURIComponent(id)}`;
savedSearchLoader.urlFor = (id: string) => `#/${encodeURIComponent(id)}`;
return savedSearchLoader;
}

View file

@ -94,8 +94,11 @@ export class EditPanelAction implements Action<ActionContext> {
public getAppTarget({ embeddable }: ActionContext): { app: string; path: string } | undefined {
const app = embeddable ? embeddable.getOutput().editApp : undefined;
const path = embeddable ? embeddable.getOutput().editPath : undefined;
let path = embeddable ? embeddable.getOutput().editPath : undefined;
if (app && path) {
if (this.currentAppId) {
path += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`;
}
return { app, path };
}
}
@ -104,11 +107,6 @@ export class EditPanelAction implements Action<ActionContext> {
let editUrl = embeddable ? embeddable.getOutput().editUrl : undefined;
if (editUrl && this.currentAppId) {
editUrl += `?${EMBEDDABLE_ORIGINATING_APP_PARAM}=${this.currentAppId}`;
// TODO: Remove this after https://github.com/elastic/kibana/pull/63443
if (this.currentAppId === 'kibana') {
editUrl += `:${window.location.hash.split(/[\/\?]/)[1]}`;
}
}
return editUrl ? editUrl : '';
}

View file

@ -20,11 +20,14 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
import { ScopedHistory } from 'kibana/public';
// @ts-ignore
import { HomeApp } from './components/home_app';
import { getServices } from './kibana_services';
export const renderApp = async (element: HTMLElement) => {
import './index.scss';
export const renderApp = async (element: HTMLElement, history: ScopedHistory) => {
const homeTitle = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' });
const { featureCatalogue, chrome } = getServices();
@ -35,7 +38,14 @@ export const renderApp = async (element: HTMLElement) => {
render(<HomeApp directories={directories} />, element);
// dispatch synthetic hash change event to update hash history objects
// this is necessary because hash updates triggered by using popState won't trigger this event naturally.
const unlisten = history.listen(() => {
window.dispatchEvent(new HashChangeEvent('hashchange'));
});
return () => {
unmountComponentAtNode(element);
unlisten();
};
};

View file

@ -73,7 +73,7 @@ exports[`apmUiEnabled 1`] = `
<EuiButton
aria-describedby="aria-describedby.addAmpButtonLabel"
className="homAddData__button"
href="#/home/tutorial/apm"
href="#/tutorial/apm"
>
<FormattedMessage
defaultMessage="Add APM"
@ -105,7 +105,7 @@ exports[`apmUiEnabled 1`] = `
aria-describedby="aria-describedby.addLogDataButtonLabel"
className="homAddData__button"
data-test-subj="logsData"
href="#/home/tutorial_directory/logging"
href="#/tutorial_directory/logging"
>
<FormattedMessage
defaultMessage="Add log data"
@ -136,7 +136,7 @@ exports[`apmUiEnabled 1`] = `
<EuiButton
aria-describedby="aria-describedby.addMetricsButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/metrics"
href="#/tutorial_directory/metrics"
>
<FormattedMessage
defaultMessage="Add metric data"
@ -202,7 +202,7 @@ exports[`apmUiEnabled 1`] = `
<EuiButton
aria-describedby="aria-describedby.addSiemButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/siem"
href="#/tutorial_directory/siem"
>
<FormattedMessage
defaultMessage="Add events"
@ -241,7 +241,7 @@ exports[`apmUiEnabled 1`] = `
/>
</strong>
<EuiLink
href="#/home/tutorial_directory/sampleData"
href="#/tutorial_directory/sampleData"
style={
Object {
"display": "block",
@ -371,7 +371,7 @@ exports[`isNewKibanaInstance 1`] = `
aria-describedby="aria-describedby.addLogDataButtonLabel"
className="homAddData__button"
data-test-subj="logsData"
href="#/home/tutorial_directory/logging"
href="#/tutorial_directory/logging"
>
<FormattedMessage
defaultMessage="Add log data"
@ -402,7 +402,7 @@ exports[`isNewKibanaInstance 1`] = `
<EuiButton
aria-describedby="aria-describedby.addMetricsButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/metrics"
href="#/tutorial_directory/metrics"
>
<FormattedMessage
defaultMessage="Add metric data"
@ -468,7 +468,7 @@ exports[`isNewKibanaInstance 1`] = `
<EuiButton
aria-describedby="aria-describedby.addSiemButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/siem"
href="#/tutorial_directory/siem"
>
<FormattedMessage
defaultMessage="Add events"
@ -507,7 +507,7 @@ exports[`isNewKibanaInstance 1`] = `
/>
</strong>
<EuiLink
href="#/home/tutorial_directory/sampleData"
href="#/tutorial_directory/sampleData"
style={
Object {
"display": "block",
@ -636,7 +636,7 @@ exports[`mlEnabled 1`] = `
<EuiButton
aria-describedby="aria-describedby.addAmpButtonLabel"
className="homAddData__button"
href="#/home/tutorial/apm"
href="#/tutorial/apm"
>
<FormattedMessage
defaultMessage="Add APM"
@ -668,7 +668,7 @@ exports[`mlEnabled 1`] = `
aria-describedby="aria-describedby.addLogDataButtonLabel"
className="homAddData__button"
data-test-subj="logsData"
href="#/home/tutorial_directory/logging"
href="#/tutorial_directory/logging"
>
<FormattedMessage
defaultMessage="Add log data"
@ -699,7 +699,7 @@ exports[`mlEnabled 1`] = `
<EuiButton
aria-describedby="aria-describedby.addMetricsButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/metrics"
href="#/tutorial_directory/metrics"
>
<FormattedMessage
defaultMessage="Add metric data"
@ -765,7 +765,7 @@ exports[`mlEnabled 1`] = `
<EuiButton
aria-describedby="aria-describedby.addSiemButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/siem"
href="#/tutorial_directory/siem"
>
<FormattedMessage
defaultMessage="Add events"
@ -804,7 +804,7 @@ exports[`mlEnabled 1`] = `
/>
</strong>
<EuiLink
href="#/home/tutorial_directory/sampleData"
href="#/tutorial_directory/sampleData"
style={
Object {
"display": "block",
@ -970,7 +970,7 @@ exports[`render 1`] = `
aria-describedby="aria-describedby.addLogDataButtonLabel"
className="homAddData__button"
data-test-subj="logsData"
href="#/home/tutorial_directory/logging"
href="#/tutorial_directory/logging"
>
<FormattedMessage
defaultMessage="Add log data"
@ -1001,7 +1001,7 @@ exports[`render 1`] = `
<EuiButton
aria-describedby="aria-describedby.addMetricsButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/metrics"
href="#/tutorial_directory/metrics"
>
<FormattedMessage
defaultMessage="Add metric data"
@ -1067,7 +1067,7 @@ exports[`render 1`] = `
<EuiButton
aria-describedby="aria-describedby.addSiemButtonLabel"
className="homAddData__button"
href="#/home/tutorial_directory/siem"
href="#/tutorial_directory/siem"
>
<FormattedMessage
defaultMessage="Add events"
@ -1106,7 +1106,7 @@ exports[`render 1`] = `
/>
</strong>
<EuiLink
href="#/home/tutorial_directory/sampleData"
href="#/tutorial_directory/sampleData"
style={
Object {
"display": "block",

View file

@ -101,7 +101,7 @@ exports[`home directories should not render directory entry when showOnHomePage
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -194,6 +194,7 @@ exports[`home directories should render ADMIN directory entry in "Manage" panel
description="Manage the index patterns that help retrieve your data from Elasticsearch."
iconType="indexPatternApp"
isBeta={false}
onClick={[Function]}
title="Index Patterns"
url="base_path/index_management_landing_page"
/>
@ -229,7 +230,7 @@ exports[`home directories should render ADMIN directory entry in "Manage" panel
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -299,6 +300,7 @@ exports[`home directories should render DATA directory entry in "Explore Data" p
description="Display and share a collection of visualizations and saved searches."
iconType="dashboardApp"
isBeta={false}
onClick={[Function]}
title="Dashboard"
url="base_path/dashboard_landing_page"
/>
@ -357,7 +359,7 @@ exports[`home directories should render DATA directory entry in "Explore Data" p
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -472,7 +474,7 @@ exports[`home isNewKibanaInstance should safely handle execeptions 1`] = `
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -587,7 +589,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -702,7 +704,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -817,7 +819,7 @@ exports[`home should render home component 1`] = `
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -932,7 +934,7 @@ exports[`home welcome should show the normal home page if loading fails 1`] = `
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -1047,7 +1049,7 @@ exports[`home welcome should show the normal home page if welcome screen is disa
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"
@ -1169,7 +1171,7 @@ exports[`home welcome stores skip welcome setting if skipped 1`] = `
/>
<EuiButton
data-test-subj="allPlugins"
href="#/home/feature_directory"
href="#/feature_directory"
>
<FormattedMessage
defaultMessage="View full directory of Kibana plugins"

View file

@ -30,12 +30,13 @@ exports[`should render popover when appLinks is not empty 1`] = `
"id": 0,
"items": Array [
Object {
"href": "root/app/kibana#/dashboard/722b74f0-b882-11e8-a6d9-e546fe2bba5f",
"href": "root/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f",
"icon": <EuiIcon
size="m"
type="dashboardApp"
/>,
"name": "Dashboard",
"onClick": [Function],
},
Object {
"href": "rootapp/myAppPath",
@ -44,6 +45,7 @@ exports[`should render popover when appLinks is not empty 1`] = `
type="logoKibana"
/>,
"name": "myAppLabel",
"onClick": [Function],
},
],
},
@ -57,7 +59,7 @@ exports[`should render simple button when appLinks is empty 1`] = `
<EuiButton
aria-label="View Sample eCommerce orders"
data-test-subj="launchSampleDataSetecommerce"
href="root/app/kibana#/dashboard/722b74f0-b882-11e8-a6d9-e546fe2bba5f"
onClick={[Function]}
>
View data
</EuiButton>

View file

@ -101,7 +101,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
footer={
<EuiButton
className="homAddData__button"
href="#/home/tutorial/apm"
href="#/tutorial/apm"
aria-describedby={apmData.ariaDescribedby}
>
<FormattedMessage id="home.addData.apm.addApmButtonLabel" defaultMessage="Add APM" />
@ -159,7 +159,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
<EuiButton
className="homAddData__button"
data-test-subj="logsData"
href="#/home/tutorial_directory/logging"
href="#/tutorial_directory/logging"
aria-describedby={loggingData.ariaDescribedby}
>
<FormattedMessage
@ -183,7 +183,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
footer={
<EuiButton
className="homAddData__button"
href="#/home/tutorial_directory/metrics"
href="#/tutorial_directory/metrics"
aria-describedby={metricsData.ariaDescribedby}
>
<FormattedMessage
@ -220,7 +220,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
footer={
<EuiButton
className="homAddData__button"
href="#/home/tutorial_directory/siem"
href="#/tutorial_directory/siem"
aria-describedby={siemData.ariaDescribedby}
>
<FormattedMessage
@ -256,7 +256,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => {
</strong>
<EuiLink
style={{ display: 'block', textAlign: 'center' }}
href="#/home/tutorial_directory/sampleData"
href="#/tutorial_directory/sampleData"
>
<FormattedMessage
id="home.addData.sampleDataLink"

View file

@ -17,13 +17,18 @@
* under the License.
*/
// make sure all dev tools are loaded and registered.
import 'uiExports/devTools';
import { getServices } from '../kibana_services';
import { npStart } from 'ui/new_platform';
if (npStart.plugins.devTools.getSortedDevTools().length === 0) {
npStart.core.chrome.navLinks.update('kibana:dev_tools', {
hidden: true,
});
}
export const createAppNavigationHandler = (targetUrl: string) => (event: MouseEvent) => {
if (event.altKey || event.metaKey || event.ctrlKey) {
return;
}
if (targetUrl.startsWith('/app/')) {
const [, appId, path] = /\/app\/(.*?)((\/|\?|#|$).*)/.exec(targetUrl) || [];
if (!appId) {
return;
}
event.preventDefault();
getServices().application.navigateToApp(appId, { path });
}
};

View file

@ -35,6 +35,7 @@ import { FeatureCatalogueCategory } from '../../services';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { createAppNavigationHandler } from './app_navigation_handler';
const ALL_TAB_ID = 'all';
const OTHERS_TAB_ID = 'others';
@ -114,6 +115,7 @@ export class FeatureDirectory extends React.Component {
return (
<EuiFlexItem key={directory.id}>
<Synopsis
onClick={createAppNavigationHandler(directory.path)}
description={directory.description}
iconType={directory.icon}
title={directory.title}

View file

@ -41,6 +41,7 @@ import { i18n } from '@kbn/i18n';
import { Welcome } from './welcome';
import { getServices } from '../kibana_services';
import { FeatureCatalogueCategory } from '../../services';
import { createAppNavigationHandler } from './app_navigation_handler';
const KEY_ENABLE_WELCOME = 'home:welcome:show';
@ -125,6 +126,7 @@ export class Home extends Component {
return (
<EuiFlexItem className="homHome__synopsisItem" key={directory.id}>
<Synopsis
onClick={createAppNavigationHandler(directory.path)}
description={directory.description}
iconType={directory.icon}
title={directory.title}
@ -203,7 +205,7 @@ export class Home extends Component {
</p>
</EuiText>
<EuiSpacer size="s" />
<EuiButton data-test-subj="allPlugins" href="#/home/feature_directory">
<EuiButton data-test-subj="allPlugins" href="#/feature_directory">
<FormattedMessage
id="home.directories.notFound.viewFullButtonLabel"
defaultMessage="View full directory of Kibana plugins"

View file

@ -24,13 +24,22 @@ import { Home } from './home';
import { FeatureDirectory } from './feature_directory';
import { TutorialDirectory } from './tutorial_directory';
import { Tutorial } from './tutorial/tutorial';
import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { HashRouter as Router, Switch, Route } from 'react-router-dom';
import { getTutorial } from '../load_tutorials';
import { replaceTemplateStrings } from './tutorial/replace_template_strings';
import { getServices } from '../kibana_services';
import { useMount } from 'react-use';
const RedirectToDefaultApp = () => {
useMount(() => {
const { kibanaLegacy } = getServices();
kibanaLegacy.navigateToDefaultApp();
});
return null;
};
export function HomeApp({ directories }) {
const {
config,
savedObjectsClient,
getBasePath,
addBasePath,
@ -42,8 +51,6 @@ export function HomeApp({ directories }) {
const mlEnabled = environment.ml;
const apmUiEnabled = environment.apmUi;
const defaultAppId = config.defaultAppId || 'discover';
const renderTutorialDirectory = props => {
return (
<TutorialDirectory
@ -71,12 +78,12 @@ export function HomeApp({ directories }) {
<I18nProvider>
<Router>
<Switch>
<Route path="/home/tutorial/:id" render={renderTutorial} />
<Route path="/home/tutorial_directory/:tab?" render={renderTutorialDirectory} />
<Route exact path="/home/feature_directory">
<Route path="/tutorial/:id" render={renderTutorial} />
<Route path="/tutorial_directory/:tab?" render={renderTutorialDirectory} />
<Route exact path="/feature_directory">
<FeatureDirectory addBasePath={addBasePath} directories={directories} />
</Route>
<Route exact path="/home">
<Route exact path="/">
<Home
addBasePath={addBasePath}
directories={directories}
@ -88,9 +95,7 @@ export function HomeApp({ directories }) {
telemetry={telemetry}
/>
</Route>
<Route path="/home">
<Redirect to={`/${defaultAppId}`} />
</Route>
<Route path="*" exact={true} component={RedirectToDefaultApp} />
</Switch>
</Router>
</I18nProvider>

View file

@ -23,6 +23,7 @@ import { EuiButton, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getServices } from '../kibana_services';
import { createAppNavigationHandler } from './app_navigation_handler';
export class SampleDataViewDataButton extends React.Component {
addBasePath = getServices().addBasePath;
@ -56,14 +57,13 @@ export class SampleDataViewDataButton extends React.Component {
},
}
);
const dashboardPath = this.addBasePath(
`/app/kibana#/dashboard/${this.props.overviewDashboard}`
);
const dashboardPath = `/app/dashboards#/view/${this.props.overviewDashboard}`;
const prefixedDashboardPath = this.addBasePath(dashboardPath);
if (this.props.appLinks.length === 0) {
return (
<EuiButton
href={dashboardPath}
onClick={createAppNavigationHandler(dashboardPath)}
data-test-subj={`launchSampleDataSet${this.props.id}`}
aria-label={viewDataButtonAriaLabel}
>
@ -77,6 +77,7 @@ export class SampleDataViewDataButton extends React.Component {
name: label,
icon: <EuiIcon type={icon} size="m" />,
href: this.addBasePath(path),
onClick: createAppNavigationHandler(path),
};
});
const panels = [
@ -88,7 +89,8 @@ export class SampleDataViewDataButton extends React.Component {
defaultMessage: 'Dashboard',
}),
icon: <EuiIcon type="dashboardApp" size="m" />,
href: dashboardPath,
href: prefixedDashboardPath,
onClick: createAppNavigationHandler(dashboardPath),
},
...additionalItems,
],

View file

@ -101,11 +101,11 @@ class TutorialUi extends React.Component {
getServices().chrome.setBreadcrumbs([
{
text: homeTitle,
href: '#/home',
href: '#/',
},
{
text: addDataTitle,
href: '#/home/tutorial_directory',
href: '#/tutorial_directory',
},
{
text: tutorial ? tutorial.name : this.props.tutorialId,
@ -324,7 +324,7 @@ class TutorialUi extends React.Component {
});
if (overviewDashboard) {
label = overviewDashboard.linkLabel;
url = this.props.addBasePath(`/app/kibana#/dashboard/${overviewDashboard.id}`);
url = this.props.addBasePath(`/app/dashboards#/view/${overviewDashboard.id}`);
}
}

View file

@ -115,7 +115,7 @@ class TutorialDirectoryUi extends React.Component {
getServices().chrome.setBreadcrumbs([
{
text: homeTitle,
href: '#/home',
href: '#/',
},
{ text: addDataTitle },
]);
@ -138,7 +138,7 @@ class TutorialDirectoryUi extends React.Component {
icon: icon,
name: tutorialConfig.name,
description: tutorialConfig.shortDescription,
url: this.props.addBasePath(`#/home/tutorial/${tutorialConfig.id}`),
url: this.props.addBasePath(`#/tutorial/${tutorialConfig.id}`),
elasticCloud: tutorialConfig.elasticCloud,
// Beta label is skipped on the tutorial overview page for now. Too many beta labels.
//isBeta: tutorialConfig.isBeta,
@ -155,7 +155,7 @@ class TutorialDirectoryUi extends React.Component {
id: 'home.tutorial.card.sampleDataDescription',
defaultMessage: 'Get started exploring Kibana with these "one click" data sets.',
}),
url: this.props.addBasePath('#/home/tutorial_directory/sampleData'),
url: this.props.addBasePath('#/tutorial_directory/sampleData'),
elasticCloud: true,
onClick: this.onSelectedTabChanged.bind(null, SAMPLE_DATA_TAB_ID),
});

Some files were not shown because too many files have changed in this diff Show more