Notably, also removes aria-hidden from many visualizations
This commit is contained in:
Michail Yasonik 2020-05-05 23:14:14 -05:00 committed by GitHub
parent 9d88805a95
commit 35e10273d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
125 changed files with 5748 additions and 691 deletions

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [AppCategory](./kibana-plugin-core-public.appcategory.md) &gt; [id](./kibana-plugin-core-public.appcategory.id.md)
## AppCategory.id property
Unique identifier for the categories
<b>Signature:</b>
```typescript
id: string;
```

View file

@ -18,6 +18,7 @@ export interface AppCategory
| --- | --- | --- |
| [ariaLabel](./kibana-plugin-core-public.appcategory.arialabel.md) | <code>string</code> | If the visual label isn't appropriate for screen readers, can override it here |
| [euiIconType](./kibana-plugin-core-public.appcategory.euiicontype.md) | <code>string</code> | Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined |
| [id](./kibana-plugin-core-public.appcategory.id.md) | <code>string</code> | Unique identifier for the categories |
| [label](./kibana-plugin-core-public.appcategory.label.md) | <code>string</code> | Label used for cateogry name. Also used as aria-label if one isn't set. |
| [order](./kibana-plugin-core-public.appcategory.order.md) | <code>number</code> | The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) |

View file

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [ChromeStart](./kibana-plugin-core-public.chromestart.md) &gt; [getNavType$](./kibana-plugin-core-public.chromestart.getnavtype_.md)
## ChromeStart.getNavType$() method
Get the navigation type TODO \#64541 Can delete
<b>Signature:</b>
```typescript
getNavType$(): Observable<NavType>;
```
<b>Returns:</b>
`Observable<NavType>`

View file

@ -58,6 +58,7 @@ core.chrome.setHelpExtension(elem => {
| [getHelpExtension$()](./kibana-plugin-core-public.chromestart.gethelpextension_.md) | Get an observable of the current custom help conttent |
| [getIsNavDrawerLocked$()](./kibana-plugin-core-public.chromestart.getisnavdrawerlocked_.md) | Get an observable of the current locked state of the nav drawer. |
| [getIsVisible$()](./kibana-plugin-core-public.chromestart.getisvisible_.md) | Get an observable of the current visibility state of the chrome. |
| [getNavType$()](./kibana-plugin-core-public.chromestart.getnavtype_.md) | Get the navigation type TODO \#64541 Can delete |
| [removeApplicationClass(className)](./kibana-plugin-core-public.chromestart.removeapplicationclass.md) | Remove a className added with <code>addApplicationClass()</code>. If className is unknown it is ignored. |
| [setAppTitle(appTitle)](./kibana-plugin-core-public.chromestart.setapptitle.md) | Sets the current app's title |
| [setBadge(badge)](./kibana-plugin-core-public.chromestart.setbadge.md) | Override the current badge |

View file

@ -158,6 +158,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [IContextProvider](./kibana-plugin-core-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
| [IToasts](./kibana-plugin-core-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-core-public.toastsapi.md)<!-- -->. |
| [MountPoint](./kibana-plugin-core-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. |
| [NavType](./kibana-plugin-core-public.navtype.md) | |
| [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
| [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | |
| [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. |

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [NavType](./kibana-plugin-core-public.navtype.md)
## NavType type
<b>Signature:</b>
```typescript
export declare type NavType = 'modern' | 'legacy';
```

View file

@ -68,6 +68,9 @@ into the document when displaying it.
`metrics:max_buckets`:: The maximum numbers of buckets that a single
data source can return. This might arise when the user selects a
short interval (for example, 1s) for a long time period (1 year).
`pageNavigation`:: The style of navigation menu for Kibana.
Choices are Legacy, the legacy style where every plugin is represented in the nav,
and Modern, a new format that bundles related plugins together in flyaway nested navigation.
`query:allowLeadingWildcards`:: Allows a wildcard (*) as the first character
in a query clause. Only applies when experimental query features are
enabled in the query bar. To disallow leading wildcards in Lucene queries,

View file

@ -125,7 +125,7 @@
"@elastic/charts": "19.2.0",
"@elastic/datemath": "5.0.3",
"@elastic/ems-client": "7.8.0",
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"@elastic/filesaver": "1.1.2",
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.4.0",

View file

@ -10,7 +10,7 @@
},
"dependencies": {
"@elastic/charts": "19.2.0",
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"@kbn/i18n": "1.0.0",
"abortcontroller-polyfill": "^1.4.0",
"angular": "^1.7.9",

View file

@ -23,7 +23,8 @@ import {
ChromeBreadcrumb,
ChromeService,
InternalChromeStart,
} from './chrome_service';
NavType,
} from './';
const createStartContractMock = () => {
const startContract: DeeplyMockedKeys<InternalChromeStart> = {
@ -72,6 +73,7 @@ const createStartContractMock = () => {
setHelpExtension: jest.fn(),
setHelpSupportUrl: jest.fn(),
getIsNavDrawerLocked$: jest.fn(),
getNavType$: jest.fn(),
};
startContract.navLinks.getAll.mockReturnValue([]);
startContract.getBrand$.mockReturnValue(new BehaviorSubject({} as ChromeBrand));
@ -81,6 +83,7 @@ const createStartContractMock = () => {
startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb]));
startContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
startContract.getIsNavDrawerLocked$.mockReturnValue(new BehaviorSubject(false));
startContract.getNavType$.mockReturnValue(new BehaviorSubject('modern' as NavType));
return startContract;
};

View file

@ -17,18 +17,18 @@
* under the License.
*/
import * as Rx from 'rxjs';
import { take, toArray } from 'rxjs/operators';
import { shallow } from 'enzyme';
import React from 'react';
import * as Rx from 'rxjs';
import { take, toArray } from 'rxjs/operators';
import { App } from '../application';
import { applicationServiceMock } from '../application/application_service.mock';
import { docLinksServiceMock } from '../doc_links/doc_links_service.mock';
import { httpServiceMock } from '../http/http_service.mock';
import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock';
import { notificationServiceMock } from '../notifications/notifications_service.mock';
import { docLinksServiceMock } from '../doc_links/doc_links_service.mock';
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
import { ChromeService } from './chrome_service';
import { App } from '../application';
class FakeApp implements App {
public title = `${this.id} App`;
@ -51,6 +51,7 @@ function defaultStartDeps(availableApps?: App[]) {
http: httpServiceMock.createStartContract(),
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createStartContract(),
};
if (availableApps) {

View file

@ -17,27 +17,26 @@
* under the License.
*/
import { Breadcrumb as EuiBreadcrumb, IconType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { BehaviorSubject, Observable, ReplaySubject, combineLatest, of, merge } from 'rxjs';
import { BehaviorSubject, combineLatest, merge, Observable, of, ReplaySubject } from 'rxjs';
import { flatMap, map, takeUntil } from 'rxjs/operators';
import { parse } from 'url';
import { i18n } from '@kbn/i18n';
import { IconType, Breadcrumb as EuiBreadcrumb } from '@elastic/eui';
import { InternalApplicationStart } from '../application';
import { DocLinksStart } from '../doc_links';
import { HttpStart } from '../http';
import { InjectedMetadataStart } from '../injected_metadata';
import { NotificationsStart } from '../notifications';
import { InternalApplicationStart } from '../application';
import { HttpStart } from '../http';
import { IUiSettingsClient } from '../ui_settings';
import { KIBANA_ASK_ELASTIC_LINK } from './constants';
import { ChromeDocTitle, DocTitleService } from './doc_title';
import { ChromeNavControls, NavControlsService } from './nav_controls';
import { ChromeNavLinks, NavLinksService } from './nav_links';
import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed';
import { NavControlsService, ChromeNavControls } from './nav_controls';
import { DocTitleService, ChromeDocTitle } from './doc_title';
import { LoadingIndicator, Header } from './ui';
import { DocLinksStart } from '../doc_links';
import { Header, LoadingIndicator } from './ui';
import { NavType } from './ui/header';
import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu';
import { KIBANA_ASK_ELASTIC_LINK } from './constants';
export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle };
const IS_LOCKED_KEY = 'core.chrome.isLocked';
@ -84,6 +83,7 @@ interface StartDeps {
http: HttpStart;
injectedMetadata: InjectedMetadataStart;
notifications: NotificationsStart;
uiSettings: IUiSettingsClient;
}
/** @internal */
@ -136,6 +136,7 @@ export class ChromeService {
http,
injectedMetadata,
notifications,
uiSettings,
}: StartDeps): Promise<InternalChromeStart> {
this.initVisibility(application);
@ -160,6 +161,10 @@ export class ChromeService {
const getIsNavDrawerLocked$ = isNavDrawerLocked$.pipe(takeUntil(this.stop$));
// TODO #64541
// Can delete
const getNavType$ = uiSettings.get$('pageNavigation').pipe(takeUntil(this.stop$));
if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) {
notifications.toasts.addWarning(
i18n.translate('core.chrome.legacyBrowserWarning', {
@ -197,6 +202,7 @@ export class ChromeService {
navControlsRight$={navControls.getRight$()}
onIsLockedUpdate={setIsNavDrawerLocked}
isLocked$={getIsNavDrawerLocked$}
navType$={getNavType$}
/>
</React.Fragment>
),
@ -257,6 +263,8 @@ export class ChromeService {
setHelpSupportUrl: (url: string) => helpSupportUrl$.next(url),
getIsNavDrawerLocked$: () => getIsNavDrawerLocked$,
getNavType$: () => getNavType$,
};
}
@ -403,6 +411,13 @@ export interface ChromeStart {
* Get an observable of the current locked state of the nav drawer.
*/
getIsNavDrawerLocked$(): Observable<boolean>;
/**
* Get the navigation type
* TODO #64541
* Can delete
*/
getNavType$(): Observable<NavType>;
}
/** @internal */

View file

@ -33,6 +33,7 @@ export {
ChromeHelpExtensionMenuDocumentationLink,
ChromeHelpExtensionMenuGitHubLink,
} from './ui/header/header_help_menu';
export { NavType } from './ui';
export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields } from './nav_links';
export { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem } from './recently_accessed';
export { ChromeNavControl, ChromeNavControls } from './nav_controls';

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,12 @@
@import '@elastic/eui/src/components/header/variables';
@import '@elastic/eui/src/components/nav_drawer/variables';
.chrHeaderWrapper {
// TODO #64541
// Delete this block
.chrHeaderWrapper:not(.headerWrapper) {
width: 100%;
position: fixed;
top: 0;
z-index: 10;
}
.chrHeaderWrapper ~ .app-wrapper:not(.hidden-chrome) {
top: $euiHeaderChildSize;
left: $euiHeaderChildSize;
// HOTFIX: Temporary fix for flyouts not inside portals
// SASSTODO: Find an actual solution
.euiFlyout {
top: $euiHeaderChildSize;
height: calc(100% - #{$euiHeaderChildSize});
}
}
.chrHeaderHelpMenu__version {
text-transform: none;
}
@ -29,19 +16,8 @@
margin-right: $euiSize;
}
// Mobile header is smaller
@include euiBreakpoint('xs', 's') {
.chrHeaderWrapper ~ .app-wrapper:not(.hidden-chrome) {
left: 0;
}
}
@include euiBreakpoint('xl') {
.chrHeaderWrapper--navIsLocked {
~ .app-wrapper:not(.hidden-chrome) {
// Shrink the content from the left so it's no longer overlapped by the nav drawer (ALWAYS)
left: $euiNavDrawerWidthExpanded !important; // sass-lint:disable-line no-important
transition: left $euiAnimSpeedFast $euiAnimSlightResistance;
}
.header__toggleNavButtonSection {
.euiBody--collapsibleNavIsDocked & {
display: none;
}
}

View file

@ -0,0 +1,136 @@
/*
* 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 { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import sinon from 'sinon';
import { CollapsibleNav } from './collapsible_nav';
import { AppCategory } from '../../../../types';
import { DEFAULT_APP_CATEGORIES } from '../../..';
import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({
htmlIdGenerator: () => () => 'mockId',
}));
const { kibana, observability, security, management } = DEFAULT_APP_CATEGORIES;
function mockLink(label: string, category?: AppCategory) {
return {
key: label,
label,
href: label,
isActive: true,
onClick: () => {},
category,
'data-test-subj': label,
};
}
function mockRecentNavLink(label: string) {
return {
href: label,
label,
title: label,
'aria-label': label,
};
}
function mockProps() {
return {
id: 'collapsible-nav',
homeHref: '/',
isLocked: false,
isOpen: false,
navLinks: [],
recentNavLinks: [],
storage: new StubBrowserStorage(),
onIsOpenUpdate: () => {},
onIsLockedUpdate: () => {},
};
}
describe('CollapsibleNav', () => {
// this test is mostly an "EUI works as expected" sanity check
it('renders the default nav', () => {
const onLock = sinon.spy();
const component = mount(<CollapsibleNav {...mockProps()} onIsLockedUpdate={onLock} />);
expect(component).toMatchSnapshot();
component.setProps({ isOpen: true });
expect(component).toMatchSnapshot();
component.setProps({ isLocked: true });
expect(component).toMatchSnapshot();
// limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element
component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click');
expect(onLock.callCount).toEqual(1);
});
it('renders links grouped by category', () => {
// just a test of category functionality, categories are not accurate
const navLinks = [
mockLink('discover', kibana),
mockLink('siem', security),
mockLink('metrics', observability),
mockLink('monitoring', management),
mockLink('visualize', kibana),
mockLink('dashboard', kibana),
mockLink('canvas'), // links should be able to be rendered top level as well
mockLink('logs', observability),
];
const recentNavLinks = [mockRecentNavLink('recent 1'), mockRecentNavLink('recent 2')];
const component = mount(
<CollapsibleNav
{...mockProps()}
isOpen={true}
navLinks={navLinks}
recentNavLinks={recentNavLinks}
/>
);
expect(component).toMatchSnapshot();
});
it('remembers collapsible section state', () => {
function expectNavLinksCount(component: ReactWrapper, count: number) {
expect(
component.find('.euiAccordion-isOpen a[data-test-subj="collapsibleNavAppLink"]').length
).toEqual(count);
}
const navLinks = [
mockLink('discover', kibana),
mockLink('siem', security),
mockLink('metrics', observability),
mockLink('monitoring', management),
mockLink('visualize', kibana),
mockLink('dashboard', kibana),
mockLink('logs', observability),
];
const component = mount(<CollapsibleNav {...mockProps()} isOpen={true} navLinks={navLinks} />);
expectNavLinksCount(component, 7);
component.find('[data-test-subj="collapsibleNavGroup-kibana"] button').simulate('click');
expectNavLinksCount(component, 4);
component.setProps({ isOpen: false });
expectNavLinksCount(component, 0); // double check the nav closed
component.setProps({ isOpen: true });
expectNavLinksCount(component, 4);
});
});

View file

@ -0,0 +1,281 @@
/*
* 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 {
EuiCollapsibleNav,
EuiCollapsibleNavGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiListGroup,
EuiListGroupItem,
EuiShowFor,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { groupBy, sortBy } from 'lodash';
import React, { useRef } from 'react';
import { AppCategory } from '../../../../types';
import { OnIsLockedUpdate } from './';
import { NavLink, RecentNavLink } from './nav_link';
function getAllCategories(allCategorizedLinks: Record<string, NavLink[]>) {
const allCategories = {} as Record<string, AppCategory | undefined>;
for (const [key, value] of Object.entries(allCategorizedLinks)) {
allCategories[key] = value[0].category;
}
return allCategories;
}
function getOrderedCategories(
mainCategories: Record<string, NavLink[]>,
categoryDictionary: ReturnType<typeof getAllCategories>
) {
return sortBy(
Object.keys(mainCategories),
categoryName => categoryDictionary[categoryName]?.order
);
}
function getCategoryLocalStorageKey(id: string) {
return `core.navGroup.${id}`;
}
function getIsCategoryOpen(id: string, storage: Storage) {
const value = storage.getItem(getCategoryLocalStorageKey(id)) ?? 'true';
return value === 'true';
}
function setIsCategoryOpen(id: string, isOpen: boolean, storage: Storage) {
storage.setItem(getCategoryLocalStorageKey(id), `${isOpen}`);
}
interface Props {
isLocked: boolean;
isOpen: boolean;
navLinks: NavLink[];
recentNavLinks: RecentNavLink[];
homeHref: string;
id: string;
storage?: Storage;
onIsLockedUpdate: OnIsLockedUpdate;
onIsOpenUpdate: (isOpen?: boolean) => void;
}
export function CollapsibleNav({
isLocked,
isOpen,
navLinks,
recentNavLinks,
onIsLockedUpdate,
onIsOpenUpdate,
homeHref,
id,
storage = window.localStorage,
}: Props) {
const lockRef = useRef<HTMLButtonElement>(null);
const groupedNavLinks = groupBy(navLinks, link => link?.category?.id);
const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks;
const categoryDictionary = getAllCategories(allCategorizedLinks);
const orderedCategories = getOrderedCategories(allCategorizedLinks, categoryDictionary);
return (
<EuiCollapsibleNav
data-test-subj="collapsibleNav"
id={id}
aria-label={i18n.translate('core.ui.primaryNav.screenReaderLabel', {
defaultMessage: 'Primary',
})}
isOpen={isOpen}
isDocked={isLocked}
onClose={onIsOpenUpdate}
>
{/* Pinned items */}
<EuiFlexItem grow={false} style={{ flexShrink: 0 }}>
<EuiCollapsibleNavGroup
background="light"
className="eui-yScroll"
style={{ maxHeight: '40vh' }}
>
<EuiListGroup
aria-label={i18n.translate('core.ui.primaryNav.pinnedLinksAriaLabel', {
defaultMessage: 'Pinned links',
})}
listItems={[
{
label: 'Home',
iconType: 'home',
href: homeHref,
onClick: () => onIsOpenUpdate(false),
},
]}
maxWidth="none"
color="text"
gutterSize="none"
size="s"
/>
</EuiCollapsibleNavGroup>
</EuiFlexItem>
<EuiHorizontalRule margin="none" />
<EuiFlexItem className="eui-yScroll">
{/* Recently viewed */}
<EuiCollapsibleNavGroup
key="recentlyViewed"
title={i18n.translate('core.ui.recentlyViewed', { defaultMessage: 'Recently viewed' })}
isCollapsible={true}
initialIsOpen={getIsCategoryOpen('recentlyViewed', storage)}
onToggle={isCategoryOpen => setIsCategoryOpen('recentlyViewed', isCategoryOpen, storage)}
>
{recentNavLinks.length > 0 ? (
<EuiListGroup
aria-label={i18n.translate('core.ui.recentlyViewedAriaLabel', {
defaultMessage: 'Recently viewed links',
})}
listItems={recentNavLinks.map(link => {
// TODO #64541
// Can remove icon from recent links completely
const { iconType, ...linkWithoutIcon } = link;
return linkWithoutIcon;
})}
maxWidth="none"
color="subdued"
gutterSize="none"
size="s"
/>
) : (
<EuiText size="s" color="subdued" style={{ padding: '0 8px 8px' }}>
<p>
{i18n.translate('core.ui.EmptyRecentlyViewed', {
defaultMessage: 'No recently viewed items',
})}
</p>
</EuiText>
)}
</EuiCollapsibleNavGroup>
{/* Kibana, Observability, Security, and Management sections */}
{orderedCategories.map((categoryName, i) => {
const category = categoryDictionary[categoryName]!;
const links = allCategorizedLinks[categoryName].map(
({ label, href, isActive, isDisabled, onClick }: NavLink) => ({
label,
href,
isActive,
isDisabled,
'data-test-subj': 'collapsibleNavAppLink',
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
onIsOpenUpdate(false);
onClick(e);
},
})
);
return (
<EuiCollapsibleNavGroup
key={category.id}
iconType={category.euiIconType}
title={category.label}
isCollapsible={true}
initialIsOpen={getIsCategoryOpen(category.id, storage)}
onToggle={isCategoryOpen => setIsCategoryOpen(category.id, isCategoryOpen, storage)}
data-test-subj={`collapsibleNavGroup-${category.id}`}
>
<EuiListGroup
aria-label={i18n.translate('core.ui.primaryNavSection.screenReaderLabel', {
defaultMessage: 'Primary navigation links, {category}',
values: { category: category.label },
})}
listItems={links}
maxWidth="none"
color="subdued"
gutterSize="none"
size="s"
/>
</EuiCollapsibleNavGroup>
);
})}
{/* Things with no category (largely for custom plugins) */}
{unknowns.map(({ label, href, icon, isActive, isDisabled, onClick }, i) => (
<EuiCollapsibleNavGroup key={i}>
<EuiListGroup flush>
<EuiListGroupItem
color="text"
size="s"
label={label}
href={href}
icon={icon}
isActive={isActive}
isDisabled={isDisabled}
data-test-subj="collapsibleNavAppLink"
onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
onIsOpenUpdate(false);
onClick(e);
}}
/>
</EuiListGroup>
</EuiCollapsibleNavGroup>
))}
{/* Docking button only for larger screens that can support it*/}
<EuiShowFor sizes={['l', 'xl']}>
<EuiCollapsibleNavGroup>
<EuiListGroup flush>
<EuiListGroupItem
data-test-subj="collapsible-nav-lock"
buttonRef={lockRef}
size="xs"
color="subdued"
label={
isLocked
? i18n.translate('core.ui.primaryNavSection.undockLabel', {
defaultMessage: 'Undock navigation',
})
: i18n.translate('core.ui.primaryNavSection.dockLabel', {
defaultMessage: 'Dock navigation',
})
}
aria-label={
isLocked
? i18n.translate('core.ui.primaryNavSection.undockAriaLabel', {
defaultMessage: 'Undock primary navigation',
})
: i18n.translate('core.ui.primaryNavSection.dockAriaLabel', {
defaultMessage: 'Dock primary navigation',
})
}
onClick={() => {
onIsLockedUpdate(!isLocked);
if (lockRef.current) {
lockRef.current.focus();
}
}}
iconType={isLocked ? 'lock' : 'lockOpen'}
/>
</EuiListGroup>
</EuiCollapsibleNavGroup>
</EuiShowFor>
</EuiFlexItem>
</EuiCollapsibleNav>
);
}

View file

@ -25,8 +25,8 @@ import {
EuiIcon,
// @ts-ignore
EuiNavDrawer,
// @ts-ignore
EuiShowFor,
htmlIdGenerator,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { Component, createRef } from 'react';
@ -43,13 +43,14 @@ import { InternalApplicationStart } from '../../../application/types';
import { HttpStart } from '../../../http';
import { ChromeHelpExtension } from '../../chrome_service';
import { HeaderBadge } from './header_badge';
import { OnIsLockedUpdate } from './';
import { NavType, OnIsLockedUpdate } from './';
import { HeaderBreadcrumbs } from './header_breadcrumbs';
import { HeaderHelpMenu } from './header_help_menu';
import { HeaderNavControls } from './header_nav_controls';
import { euiNavLink } from './nav_link';
import { createNavLink, createRecentNavLink } from './nav_link';
import { HeaderLogo } from './header_logo';
import { NavDrawer } from './nav_drawer';
import { CollapsibleNav } from './collapsible_nav';
export interface HeaderProps {
kibanaVersion: string;
@ -70,6 +71,7 @@ export interface HeaderProps {
navControlsRight$: Rx.Observable<readonly ChromeNavControl[]>;
basePath: HttpStart['basePath'];
isLocked$: Rx.Observable<boolean>;
navType$: Rx.Observable<NavType>;
onIsLockedUpdate: OnIsLockedUpdate;
}
@ -83,11 +85,14 @@ interface State {
navControlsRight: readonly ChromeNavControl[];
currentAppId: string | undefined;
isLocked: boolean;
navType: NavType;
isOpen: boolean;
}
export class Header extends Component<HeaderProps, State> {
private subscription?: Rx.Subscription;
private navDrawerRef = createRef<EuiNavDrawer>();
private toggleCollapsibleNavRef = createRef<HTMLButtonElement>();
constructor(props: HeaderProps) {
super(props);
@ -105,6 +110,8 @@ export class Header extends Component<HeaderProps, State> {
navControlsRight: [],
currentAppId: '',
isLocked,
navType: 'modern',
isOpen: false,
};
}
@ -120,7 +127,8 @@ export class Header extends Component<HeaderProps, State> {
this.props.navControlsLeft$,
this.props.navControlsRight$,
this.props.application.currentAppId$,
this.props.isLocked$
this.props.isLocked$,
this.props.navType$
)
).subscribe({
next: ([
@ -129,7 +137,7 @@ export class Header extends Component<HeaderProps, State> {
forceNavigation,
navLinks,
recentlyAccessed,
[navControlsLeft, navControlsRight, currentAppId, isLocked],
[navControlsLeft, navControlsRight, currentAppId, isLocked, navType],
]) => {
this.setState({
appTitle,
@ -141,6 +149,7 @@ export class Header extends Component<HeaderProps, State> {
navControlsRight,
currentAppId,
isLocked,
navType,
});
},
});
@ -176,7 +185,7 @@ export class Header extends Component<HeaderProps, State> {
kibanaVersion,
} = this.props;
const navLinks = this.state.navLinks.map(link =>
euiNavLink(
createNavLink(
link,
this.props.legacyMode,
this.state.currentAppId,
@ -184,26 +193,54 @@ export class Header extends Component<HeaderProps, State> {
this.props.application.navigateToApp
)
);
const recentNavLinks = this.state.recentlyAccessed.map(link =>
createRecentNavLink(link, this.state.navLinks, this.props.basePath)
);
if (!isVisible) {
return null;
}
const className = classnames(
'chrHeaderWrapper',
'chrHeaderWrapper', // TODO #64541 - delete this
'hide-for-sharing',
{
'chrHeaderWrapper--navIsLocked': this.state.isLocked,
},
'hide-for-sharing'
headerWrapper: this.state.navType === 'modern',
}
);
const navId = htmlIdGenerator()();
return (
<header className={className} data-test-subj="headerGlobalNav">
<EuiHeader>
<EuiHeader position="fixed">
<EuiHeaderSection grow={false}>
<EuiShowFor sizes={['xs', 's']}>
<EuiHeaderSectionItem border="right">{this.renderMenuTrigger()}</EuiHeaderSectionItem>
</EuiShowFor>
{this.state.navType === 'modern' ? (
<EuiHeaderSectionItem border="right" className="header__toggleNavButtonSection">
<EuiHeaderSectionItemButton
data-test-subj="toggleNavButton"
aria-label={i18n.translate('core.ui.primaryNav.toggleNavAriaLabel', {
defaultMessage: 'Toggle primary navigation',
})}
onClick={() => {
this.setState({ isOpen: !this.state.isOpen });
}}
aria-expanded={this.state.isOpen}
aria-pressed={this.state.isOpen}
aria-controls={navId}
ref={this.toggleCollapsibleNavRef}
>
<EuiIcon type="menu" size="m" />
</EuiHeaderSectionItemButton>
</EuiHeaderSectionItem>
) : (
// TODO #64541
// Delete this block
<EuiShowFor sizes={['xs', 's']}>
<EuiHeaderSectionItem border="right">
{this.renderMenuTrigger()}
</EuiHeaderSectionItem>
</EuiShowFor>
)}
<EuiHeaderSectionItem border="right">
<HeaderLogo
@ -235,15 +272,33 @@ export class Header extends Component<HeaderProps, State> {
<HeaderNavControls side="right" navControls={navControlsRight} />
</EuiHeaderSection>
</EuiHeader>
<NavDrawer
isLocked={this.state.isLocked}
onIsLockedUpdate={this.props.onIsLockedUpdate}
navLinks={navLinks}
chromeNavLinks={this.state.navLinks}
recentlyAccessedItems={this.state.recentlyAccessed}
basePath={this.props.basePath}
ref={this.navDrawerRef}
/>
{this.state.navType === 'modern' ? (
<CollapsibleNav
id={navId}
isLocked={this.state.isLocked}
onIsLockedUpdate={this.props.onIsLockedUpdate}
navLinks={navLinks}
recentNavLinks={recentNavLinks}
isOpen={this.state.isOpen}
homeHref={this.props.homeHref}
onIsOpenUpdate={(isOpen = !this.state.isOpen) => {
this.setState({ isOpen });
if (this.toggleCollapsibleNavRef.current) {
this.toggleCollapsibleNavRef.current.focus();
}
}}
/>
) : (
// TODO #64541
// Delete this block
<NavDrawer
isLocked={this.state.isLocked}
onIsLockedUpdate={this.props.onIsLockedUpdate}
navLinks={navLinks}
recentNavLinks={recentNavLinks}
ref={this.navDrawerRef}
/>
)}
</header>
);
}

View file

@ -18,6 +18,7 @@
*/
export { Header, HeaderProps } from './header';
export { OnIsLockedUpdate, NavType } from './types';
export {
ChromeHelpExtensionMenuLink,
ChromeHelpExtensionMenuCustomLink,
@ -25,4 +26,3 @@ export {
ChromeHelpExtensionMenuDocumentationLink,
ChromeHelpExtensionMenuGitHubLink,
} from './header_help_menu';
export type OnIsLockedUpdate = (isLocked: boolean) => void;

View file

@ -22,22 +22,18 @@ import { i18n } from '@kbn/i18n';
// @ts-ignore
import { EuiNavDrawer, EuiHorizontalRule, EuiNavDrawerGroup } from '@elastic/eui';
import { OnIsLockedUpdate } from './';
import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../../..';
import { HttpStart } from '../../../http';
import { NavLink } from './nav_link';
import { NavLink, RecentNavLink } from './nav_link';
import { RecentLinks } from './recent_links';
export interface Props {
isLocked?: boolean;
onIsLockedUpdate?: OnIsLockedUpdate;
navLinks: NavLink[];
chromeNavLinks: ChromeNavLink[];
recentlyAccessedItems: ChromeRecentlyAccessedHistoryItem[];
basePath: HttpStart['basePath'];
recentNavLinks: RecentNavLink[];
}
function navDrawerRenderer(
{ isLocked, onIsLockedUpdate, navLinks, chromeNavLinks, recentlyAccessedItems, basePath }: Props,
{ isLocked, onIsLockedUpdate, navLinks, recentNavLinks }: Props,
ref: React.Ref<HTMLElement>
) {
return (
@ -50,11 +46,7 @@ function navDrawerRenderer(
defaultMessage: 'Primary',
})}
>
{RecentLinks({
recentlyAccessedItems,
navLinks: chromeNavLinks,
basePath,
})}
{RecentLinks({ recentNavLinks })}
<EuiHorizontalRule margin="none" />
<EuiNavDrawerGroup
data-test-subj="navDrawerAppsMenu"

View file

@ -18,11 +18,13 @@
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiImage } from '@elastic/eui';
import { ChromeNavLink, CoreStart } from '../../../';
import { AppCategory } from 'src/core/types';
import { ChromeNavLink, CoreStart, ChromeRecentlyAccessedHistoryItem } from '../../../';
import { HttpStart } from '../../../http';
function isModifiedEvent(event: MouseEvent) {
function isModifiedEvent(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
@ -30,15 +32,36 @@ function LinkIcon({ url }: { url: string }) {
return <EuiImage size="s" alt="" aria-hidden={true} url={url} />;
}
export type NavLink = ReturnType<typeof euiNavLink>;
export interface NavLink {
key: string;
label: string;
href: string;
isActive: boolean;
onClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void;
category?: AppCategory;
isDisabled?: boolean;
iconType?: string;
icon?: JSX.Element;
order?: number;
'data-test-subj': string;
}
export function euiNavLink(
/**
* Create a link that's actually ready to be passed into EUI
*
* @param navLink
* @param legacyMode
* @param currentAppId
* @param basePath
* @param navigateToApp
*/
export function createNavLink(
navLink: ChromeNavLink,
legacyMode: boolean,
currentAppId: string | undefined,
basePath: HttpStart['basePath'],
navigateToApp: CoreStart['application']['navigateToApp']
) {
): NavLink {
const {
legacy,
url,
@ -64,7 +87,7 @@ export function euiNavLink(
key: id,
label: tooltip ?? title,
href, // Use href and onClick to support "open in new tab" and SPA navigation in the same link
onClick(event: MouseEvent) {
onClick(event) {
if (
!legacyMode && // ignore when in legacy mode
!legacy && // ignore links to legacy apps
@ -85,3 +108,76 @@ export function euiNavLink(
'data-test-subj': 'navDrawerAppsMenuLink',
};
}
// Providing a buffer between the limit and the cut off index
// protects from truncating just the last couple (6) characters
const TRUNCATE_LIMIT: number = 64;
const TRUNCATE_AT: number = 58;
function truncateRecentItemLabel(label: string): string {
if (label.length > TRUNCATE_LIMIT) {
label = `${label.substring(0, TRUNCATE_AT)}`;
}
return label;
}
/**
* @param {string} url - a relative or root relative url. If a relative path is given then the
* absolute url returned will depend on the current page where this function is called from. For example
* if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get
* back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that
* starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart".
* @return {string} the relative url transformed into an absolute url
*/
function relativeToAbsolute(url: string) {
const a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
}
export interface RecentNavLink {
href: string;
label: string;
title: string;
'aria-label': string;
iconType?: string;
}
/**
* Add saved object type info to recently links
*
* Recent nav links are similar to normal nav links but are missing some Kibana Platform magic and
* because of legacy reasons have slightly different properties.
* @param recentLink
* @param navLinks
* @param basePath
*/
export function createRecentNavLink(
recentLink: ChromeRecentlyAccessedHistoryItem,
navLinks: ChromeNavLink[],
basePath: HttpStart['basePath']
) {
const { link, label } = recentLink;
const href = relativeToAbsolute(basePath.prepend(link));
const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase));
let titleAndAriaLabel = label;
if (navLink) {
titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', {
defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}',
values: {
recentlyAccessedItemLinklabel: label,
pageType: navLink.title,
},
});
}
return {
href,
label: truncateRecentItemLabel(label),
title: titleAndAriaLabel,
'aria-label': titleAndAriaLabel,
iconType: navLink?.euiIconType,
};
}

View file

@ -21,73 +21,13 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { EuiNavDrawerGroup } from '@elastic/eui';
import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem } from '../../..';
import { HttpStart } from '../../../http';
// Providing a buffer between the limit and the cut off index
// protects from truncating just the last couple (6) characters
const TRUNCATE_LIMIT: number = 64;
const TRUNCATE_AT: number = 58;
export function truncateRecentItemLabel(label: string): string {
if (label.length > TRUNCATE_LIMIT) {
label = `${label.substring(0, TRUNCATE_AT)}`;
}
return label;
}
/**
* @param {string} url - a relative or root relative url. If a relative path is given then the
* absolute url returned will depend on the current page where this function is called from. For example
* if you are on page "http://www.mysite.com/shopping/kids" and you pass this function "adults", you would get
* back "http://www.mysite.com/shopping/adults". If you passed this function a root relative path, or one that
* starts with a "/", for example "/account/cart", you would get back "http://www.mysite.com/account/cart".
* @return {string} the relative url transformed into an absolute url
*/
function relativeToAbsolute(url: string) {
const a = document.createElement('a');
a.setAttribute('href', url);
return a.href;
}
function prepareForEUI(
recentlyAccessed: ChromeRecentlyAccessedHistoryItem[],
navLinks: ChromeNavLink[],
basePath: HttpStart['basePath']
) {
return recentlyAccessed.map(({ link, label }) => {
const href = relativeToAbsolute(basePath.prepend(link));
const navLink = navLinks.find(nl => href.startsWith(nl.baseUrl ?? nl.subUrlBase));
let titleAndAriaLabel = label;
if (navLink) {
titleAndAriaLabel = i18n.translate('core.ui.recentLinks.linkItem.screenReaderLabel', {
defaultMessage: '{recentlyAccessedItemLinklabel}, type: {pageType}',
values: {
recentlyAccessedItemLinklabel: label,
pageType: navLink.title,
},
});
}
return {
href,
label: truncateRecentItemLabel(label),
title: titleAndAriaLabel,
'aria-label': titleAndAriaLabel,
iconType: navLink?.euiIconType,
};
});
}
import { RecentNavLink } from './nav_link';
interface Props {
recentlyAccessedItems: ChromeRecentlyAccessedHistoryItem[];
navLinks: ChromeNavLink[];
basePath: HttpStart['basePath'];
recentNavLinks: RecentNavLink[];
}
export function RecentLinks({ recentlyAccessedItems, navLinks, basePath }: Props) {
export function RecentLinks({ recentNavLinks }: Props) {
return (
<EuiNavDrawerGroup
listItems={[
@ -96,12 +36,12 @@ export function RecentLinks({ recentlyAccessedItems, navLinks, basePath }: Props
defaultMessage: 'Recently viewed',
}),
iconType: 'recentlyViewedApp',
isDisabled: recentlyAccessedItems.length === 0,
isDisabled: recentNavLinks.length === 0,
flyoutMenu: {
title: i18n.translate('core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle', {
defaultMessage: 'Recent items',
}),
listItems: prepareForEUI(recentlyAccessedItems, navLinks, basePath),
listItems: recentNavLinks,
},
},
]}

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
export type OnIsLockedUpdate = (isLocked: boolean) => void;
export type NavType = 'modern' | 'legacy';

View file

@ -25,4 +25,5 @@ export {
ChromeHelpExtensionMenuDiscussLink,
ChromeHelpExtensionMenuDocumentationLink,
ChromeHelpExtensionMenuGitHubLink,
NavType,
} from './header';

View file

@ -240,6 +240,7 @@ export class CoreSystem {
http,
injectedMetadata,
notifications,
uiSettings,
});
application.registerMountContext(this.coreContext.coreId, 'core', () => ({

View file

@ -54,6 +54,7 @@ import {
ChromeStart,
ChromeRecentlyAccessed,
ChromeRecentlyAccessedHistoryItem,
NavType,
} from './chrome';
import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors';
import { HttpSetup, HttpStart } from './http';
@ -354,4 +355,5 @@ export {
PluginOpaqueId,
IUiSettingsClient,
UiSettingsState,
NavType,
};

View file

@ -13,12 +13,7 @@ Array [
Array [
<mockConstructor>
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<MountWrapper
className="kbnOverlayMountWrapper"
@ -31,19 +26,14 @@ Array [
]
`;
exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"<div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div role=\\"dialog\\" class=\\"euiFlyout euiFlyout--medium\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiFlyout__closeButton\\" type=\\"button\\" aria-label=\\"Closes this dialog\\" data-test-subj=\\"euiFlyoutCloseButton\\"><div data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\"></div></button><div class=\\"kbnOverlayMountWrapper\\"><span>Flyout content</span></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div>"`;
exports[`FlyoutService openFlyout() renders a flyout to the DOM 2`] = `"<div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div role=\\"dialog\\" class=\\"euiFlyout euiFlyout--medium\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiFlyout__closeButton\\" type=\\"button\\" aria-label=\\"Close this dialog\\" data-test-subj=\\"euiFlyoutCloseButton\\"><div data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\"></div></button><div class=\\"kbnOverlayMountWrapper\\"><span>Flyout content</span></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div>"`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 1`] = `
Array [
Array [
<mockConstructor>
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<MountWrapper
className="kbnOverlayMountWrapper"
@ -56,12 +46,7 @@ Array [
Array [
<mockConstructor>
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<MountWrapper
className="kbnOverlayMountWrapper"
@ -74,4 +59,4 @@ Array [
]
`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"<div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div role=\\"dialog\\" class=\\"euiFlyout euiFlyout--medium\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiFlyout__closeButton\\" type=\\"button\\" aria-label=\\"Closes this dialog\\" data-test-subj=\\"euiFlyoutCloseButton\\"><div data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\"></div></button><div class=\\"kbnOverlayMountWrapper\\"><span>Flyout content 2</span></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div>"`;
exports[`FlyoutService openFlyout() with a currently active flyout replaces the current flyout with a new one 2`] = `"<div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"false\\"><div role=\\"dialog\\" class=\\"euiFlyout euiFlyout--medium\\" tabindex=\\"0\\"><button class=\\"euiButtonIcon euiButtonIcon--text euiFlyout__closeButton\\" type=\\"button\\" aria-label=\\"Close this dialog\\" data-test-subj=\\"euiFlyoutCloseButton\\"><div data-euiicon-type=\\"cross\\" class=\\"euiButtonIcon__icon\\" aria-hidden=\\"true\\"></div></button><div class=\\"kbnOverlayMountWrapper\\"><span>Flyout content 2</span></div></div></div><div data-focus-guard=\\"true\\" tabindex=\\"0\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div></div>"`;

View file

@ -55,6 +55,7 @@ export interface AppBase {
export interface AppCategory {
ariaLabel?: string;
euiIconType?: string;
id: string;
label: string;
order?: number;
}
@ -343,6 +344,7 @@ export interface ChromeStart {
getHelpExtension$(): Observable<ChromeHelpExtension | undefined>;
getIsNavDrawerLocked$(): Observable<boolean>;
getIsVisible$(): Observable<boolean>;
getNavType$(): Observable<NavType>;
navControls: ChromeNavControls;
navLinks: ChromeNavLinks;
recentlyAccessed: ChromeRecentlyAccessed;
@ -443,23 +445,28 @@ export function deepFreeze<T extends Freezable>(object: T): RecursiveReadonly<T>
// @internal (undocumented)
export const DEFAULT_APP_CATEGORIES: Readonly<{
analyze: {
kibana: {
id: string;
label: string;
euiIconType: string;
order: number;
};
observability: {
id: string;
label: string;
euiIconType: string;
order: number;
};
security: {
id: string;
label: string;
order: number;
euiIconType: string;
};
management: {
id: string;
label: string;
euiIconType: string;
order: number;
};
}>;
@ -883,6 +890,11 @@ export function modifyUrl(url: string, urlModifier: (urlParts: URLMeaningfulPart
// @public
export type MountPoint<T extends HTMLElement = HTMLElement> = (element: T) => UnmountCallback;
// Warning: (ae-missing-release-tag) "NavType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type NavType = 'modern' | 'legacy';
// @public (undocumented)
export interface NotificationsSetup {
// (undocumented)

View file

@ -1,3 +1,6 @@
@import '@elastic/eui/src/components/header/variables';
@import '@elastic/eui/src/components/nav_drawer/variables';
/**
* stretch the root element of the Kibana application to set the base-size that
* flexed children should keep. Only works when paired with root styles applied
@ -9,7 +12,9 @@
min-height: 100%;
}
.app-wrapper {
// TODO #64541
// Delete this block
.chrHeaderWrapper:not(.headerWrapper) ~ .app-wrapper {
display: flex;
flex-flow: column nowrap;
position: absolute;
@ -20,6 +25,22 @@
z-index: 5;
margin: 0 auto;
&:not(.hidden-chrome) {
top: $euiHeaderChildSize;
left: $euiHeaderChildSize;
// HOTFIX: Temporary fix for flyouts not inside portals
// SASSTODO: Find an actual solution
.euiFlyout {
top: $euiHeaderChildSize;
height: calc(100% - #{$euiHeaderChildSize});
}
@include euiBreakpoint('xs', 's') {
left: 0;
}
}
/**
* 1. Dirty, but we need to override the .kbnGlobalNav-isOpen state
* when we're looking at the log-in screen.
@ -33,6 +54,32 @@
}
}
// TODO #64541
// Delete this block
@include euiBreakpoint('xl') {
.chrHeaderWrapper--navIsLocked:not(.headerWrapper) {
~ .app-wrapper:not(.hidden-chrome) {
// Shrink the content from the left so it's no longer overlapped by the nav drawer (ALWAYS)
left: $euiNavDrawerWidthExpanded !important; // sass-lint:disable-line no-important
transition: left $euiAnimSpeedFast $euiAnimSlightResistance;
}
}
}
// TODO #64541
// Remove .headerWrapper and header conditionals
.headerWrapper ~ .app-wrapper,
:not(header) ~ .app-wrapper {
display: flex;
flex-flow: column nowrap;
margin: 0 auto;
min-height: calc(100vh - #{$euiHeaderHeightCompensation});
&.hidden-chrome {
min-height: 100vh;
}
}
.app-wrapper-panel {
display: flex;
flex-grow: 1;

View file

@ -133,6 +133,7 @@ describe('getNavLinks', () => {
id: 'app-a',
title: 'AppA',
category: {
id: 'foo',
label: 'My Category',
},
order: 42,
@ -151,6 +152,7 @@ describe('getNavLinks', () => {
id: 'app-a',
title: 'AppA',
category: {
id: 'foo',
label: 'My Category',
},
order: 42,
@ -211,6 +213,7 @@ describe('getNavLinks', () => {
id: 'link-a',
title: 'AppA',
category: {
id: 'foo',
label: 'My Second Cat',
},
order: 72,
@ -232,6 +235,7 @@ describe('getNavLinks', () => {
id: 'link-a',
title: 'AppA',
category: {
id: 'foo',
label: 'My Second Cat',
},
order: 72,

View file

@ -700,23 +700,28 @@ export function deepFreeze<T extends Freezable>(object: T): RecursiveReadonly<T>
// @internal (undocumented)
export const DEFAULT_APP_CATEGORIES: Readonly<{
analyze: {
kibana: {
id: string;
label: string;
euiIconType: string;
order: number;
};
observability: {
id: string;
label: string;
euiIconType: string;
order: number;
};
security: {
id: string;
label: string;
order: number;
euiIconType: string;
};
management: {
id: string;
label: string;
euiIconType: string;
order: number;
};
}>;

View file

@ -24,6 +24,11 @@
* @public
*/
export interface AppCategory {
/**
* Unique identifier for the categories
*/
id: string;
/**
* Label used for cateogry name.
* Also used as aria-label if one isn't set.

View file

@ -21,13 +21,16 @@ import { i18n } from '@kbn/i18n';
/** @internal */
export const DEFAULT_APP_CATEGORIES = Object.freeze({
analyze: {
label: i18n.translate('core.ui.analyzeNavList.label', {
defaultMessage: 'Analyze',
kibana: {
id: 'kibana',
label: i18n.translate('core.ui.kibanaNavList.label', {
defaultMessage: 'Kibana',
}),
euiIconType: 'logoKibana',
order: 1000,
},
observability: {
id: 'observability',
label: i18n.translate('core.ui.observabilityNavList.label', {
defaultMessage: 'Observability',
}),
@ -35,6 +38,7 @@ export const DEFAULT_APP_CATEGORIES = Object.freeze({
order: 2000,
},
security: {
id: 'security',
label: i18n.translate('core.ui.securityNavList.label', {
defaultMessage: 'Security',
}),
@ -42,9 +46,10 @@ export const DEFAULT_APP_CATEGORIES = Object.freeze({
euiIconType: 'logoSecurity',
},
management: {
id: 'management',
label: i18n.translate('core.ui.managementNavList.label', {
defaultMessage: 'Management',
}),
euiIconType: 'managementApp',
order: 5000,
},
});

View file

@ -67,33 +67,33 @@ export default function(kibana) {
title: i18n.translate('kbn.discoverTitle', {
defaultMessage: 'Discover',
}),
order: -1003,
order: 2000,
url: `${kbnBaseUrl}#/discover`,
euiIconType: 'discoverApp',
disableSubUrlTracking: true,
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
},
{
id: 'kibana:visualize',
title: i18n.translate('kbn.visualizeTitle', {
defaultMessage: 'Visualize',
}),
order: -1002,
order: 7000,
url: `${kbnBaseUrl}#/visualize`,
euiIconType: 'visualizeApp',
disableSubUrlTracking: true,
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
},
{
id: 'kibana:dashboard',
title: i18n.translate('kbn.dashboardTitle', {
defaultMessage: 'Dashboard',
}),
order: -1001,
order: 1000,
url: `${kbnBaseUrl}#/dashboards`,
euiIconType: 'dashboardApp',
disableSubUrlTracking: true,
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
},
{
id: 'kibana:dev_tools',
@ -108,7 +108,7 @@ export default function(kibana) {
{
id: 'kibana:stack_management',
title: i18n.translate('kbn.managementTitle', {
defaultMessage: 'Management',
defaultMessage: 'Stack Management',
}),
order: 9003,
url: `${kbnBaseUrl}#/management`,

View file

@ -69,7 +69,7 @@ export function updateLandingPage(version) {
<h1>
<FormattedMessage
id="kbn.management.landing.header"
defaultMessage="Kibana {version} management"
defaultMessage="Welcome to Stack Management {version}"
values={{ version }}
/>
</h1>

View file

@ -1172,5 +1172,25 @@ export function getUiSettingDefaults() {
category: ['accessibility'],
requiresPageReload: true,
},
pageNavigation: {
name: i18n.translate('kbn.advancedSettings.pageNavigationName', {
defaultMessage: 'Side nav style',
}),
value: 'modern',
description: i18n.translate('kbn.advancedSettings.pageNavigationDesc', {
defaultMessage: 'Change the style of navigation',
}),
type: 'select',
options: ['modern', 'legacy'],
optionLabels: {
modern: i18n.translate('kbn.advancedSettings.pageNavigationModern', {
defaultMessage: 'Modern',
}),
legacy: i18n.translate('kbn.advancedSettings.pageNavigationLegacy', {
defaultMessage: 'Legacy',
}),
},
schema: schema.oneOf([schema.literal('modern'), schema.literal('legacy')]),
},
};
}

View file

@ -54,11 +54,11 @@ const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPl
uiExports: {
app: {
title: 'Timelion',
order: -1000,
order: 8000,
icon: 'plugins/timelion/icon.svg',
euiIconType: 'timelionApp',
main: 'plugins/timelion/app',
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
},
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
hacks: [resolve(__dirname, 'public/legacy')],

View file

@ -4,12 +4,7 @@ exports[`LabelTemplateFlyout should not render if not visible 1`] = `""`;
exports[`LabelTemplateFlyout should render normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutBody>
<EuiText>

View file

@ -4,12 +4,7 @@ exports[`UrlTemplateFlyout should not render if not visible 1`] = `""`;
exports[`UrlTemplateFlyout should render normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutBody>
<EuiText>

View file

@ -2,13 +2,8 @@
exports[`ScriptingHelpFlyout should render normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
data-test-subj="scriptedFieldsHelpFlyout"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutBody>
<EuiTabbedContent
@ -53,13 +48,8 @@ exports[`ScriptingHelpFlyout should render normally 1`] = `
exports[`ScriptingHelpFlyout should render nothing if not visible 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
data-test-subj="scriptedFieldsHelpFlyout"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutBody>
<EuiTabbedContent

View file

@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n';
export const MANAGEMENT_BREADCRUMB = Object.freeze({
text: i18n.translate('common.ui.stackManagement.breadcrumb', {
defaultMessage: 'Management',
defaultMessage: 'Stack Management',
}),
href: '#/management',
});

View file

@ -1,3 +1,5 @@
@import '@elastic/eui/src/components/collapsible_nav/variables';
// Forms
// Angular form states
@ -38,7 +40,9 @@ input[type='checkbox'],
// Application Layout
// chrome-context
.content {
// TODO #64541
// Delete this block
.chrHeaderWrapper:not(.headerWrapper) .content {
display: flex;
flex-flow: row nowrap;
width: 100%;
@ -119,7 +123,7 @@ input[type='checkbox'],
}
}
// A neccessary hack so that the above focus policy doesn't polute some EUI
// A necessary hack so that the above focus policy doesn't pollute some EUI
// entrenched inputs.
.euiComboBox {
// :not() specificity needed to override the above
@ -128,6 +132,10 @@ input[type='checkbox'],
}
}
.euiBody--collapsibleNavIsDocked .euiBottomBar {
margin-left: $euiCollapsibleNavWidth;
}
// Utility classes
.fullWidth {

View file

@ -1,6 +1,8 @@
@import '@elastic/eui/src/components/header/variables';
@import '@elastic/eui/src/components/nav_drawer/variables';
// TODO #64541
// Delete this whole file
.mgtAdvancedSettingsForm__bottomBar {
margin-left: $euiNavDrawerWidthCollapsed;
z-index: 9; // Puts it inuder the nav drawer when expanded

View file

@ -19,6 +19,7 @@
import React, { PureComponent, Fragment } from 'react';
import classNames from 'classnames';
import {
EuiFlexGroup,
EuiFlexItem,
@ -325,10 +326,18 @@ export class Form extends PureComponent<FormProps> {
renderBottomBar = () => {
const areChangesInvalid = this.areChangesInvalid();
const bottomBarClasses = classNames('mgtAdvancedSettingsForm__bottomBar', {
'mgtAdvancedSettingsForm__bottomBar--pushForNav':
localStorage.getItem(NAV_IS_LOCKED_KEY) === 'true',
});
// TODO #64541
// Delete these classes
let bottomBarClasses = '';
const pageNav = this.props.settings.general.find(setting => setting.name === 'pageNavigation');
if (pageNav?.value === 'legacy') {
bottomBarClasses = classNames('mgtAdvancedSettingsForm__bottomBar', {
'mgtAdvancedSettingsForm__bottomBar--pushForNav':
localStorage.getItem(NAV_IS_LOCKED_KEY) === 'true',
});
}
return (
<EuiBottomBar className={bottomBarClasses} data-test-subj="advancedSetting-bottomBar">
<EuiFlexGroup

View file

@ -1,7 +1,8 @@
.dshAppContainer {
display: flex;
flex-direction: column;
height: 100%;
height: 100%; // TODO #64541 - can delete this
flex: 1;
}
.dshStartScreen {

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { Component } from 'react';
import React from 'react';
import { debounce } from 'lodash';
import { withKibana, KibanaReactContextValue } from '../../../../../kibana_react/public';
@ -39,7 +39,7 @@ export interface PhraseSuggestorState {
* aggregatable), we pull out the common logic for requesting suggestions into this component
* which both of them extend.
*/
export class PhraseSuggestorUI<T extends PhraseSuggestorProps> extends Component<
export class PhraseSuggestorUI<T extends PhraseSuggestorProps> extends React.Component<
T,
PhraseSuggestorState
> {

View file

@ -180,6 +180,7 @@ export function SavedQueryManagementComponent({
}}
anchorPosition="downLeft"
panelPaddingSize="none"
buffer={-8}
ownFocus
>
<div

View file

@ -2,13 +2,9 @@
exports[`render 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
data-test-subj="loadSearchForm"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={true}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}

View file

@ -61,9 +61,7 @@ export const PanelOptionsMenu: React.FC<PanelOptionsMenuProps> = ({
);
const ariaLabelWithoutTitle = i18n.translate(
'embeddableApi.panel.optionsMenu.panelOptionsButtonAriaLabel',
{
defaultMessage: 'Panel options',
}
{ defaultMessage: 'Panel options' }
);
const button = (

View file

@ -8,6 +8,7 @@ exports[`disableMsg 1`] = `
label="list control"
>
<EuiFieldText
aria-label="Select..."
disabled={true}
placeholder="Select..."
/>

View file

@ -114,6 +114,10 @@ class ListControlUi extends PureComponent<ListControlUiProps, ListControlUiState
if (this.props.disableMsg) {
return (
<EuiFieldText
aria-label={intl.formatMessage({
id: 'inputControl.vis.listControl.selectTextPlaceholder',
defaultMessage: 'Select...',
})}
placeholder={intl.formatMessage({
id: 'inputControl.vis.listControl.selectTextPlaceholder',
defaultMessage: 'Select...',

View file

@ -16,8 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
import React, { Component, createRef } from 'react';
import { EuiFormRow, EuiDualRange } from '@elastic/eui';
import { EuiFormRowDisplayKeys } from '@elastic/eui/src/components/form/form_row/form_row';
import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range';
@ -35,8 +35,8 @@ interface Props extends Omit<EuiDualRangeProps, 'value' | 'onChange' | 'min' | '
label?: string;
formRowDisplay?: EuiFormRowDisplayKeys;
onChange?: (val: [string, string]) => void;
min?: ValueMember;
max?: ValueMember;
min?: number;
max?: number;
}
interface State {
@ -72,6 +72,18 @@ export class ValidatedDualRange extends Component<Props> {
return null;
}
// Can remove after eui#3412 is resolved
componentDidMount() {
if (this.trackRef.current) {
const track = this.trackRef.current.querySelector('.euiRangeTrack');
if (track) {
track.setAttribute('aria-hidden', 'true');
}
}
}
trackRef = createRef<HTMLDivElement>();
// @ts-ignore state populated by getDerivedStateFromProps
state: State = {};
@ -103,29 +115,38 @@ export class ValidatedDualRange extends Component<Props> {
value, // eslint-disable-line no-unused-vars
onChange, // eslint-disable-line no-unused-vars
allowEmptyRange, // eslint-disable-line no-unused-vars
// @ts-ignore
...rest // TODO: Consider alternatives for spread operator in component
} = this.props;
return (
<EuiFormRow
compressed={compressed}
fullWidth={fullWidth}
isInvalid={!this.state.isValid}
error={this.state.errorMessage ? [this.state.errorMessage] : []}
label={label}
display={formRowDisplay}
>
<EuiDualRange
<div ref={this.trackRef}>
<EuiFormRow
compressed={compressed}
fullWidth={fullWidth}
value={this.state.value}
onChange={this._onChange}
// @ts-ignore
focusable={false} // remove when #59039 is fixed
{...rest}
/>
</EuiFormRow>
isInvalid={!this.state.isValid}
error={this.state.errorMessage ? [this.state.errorMessage] : []}
label={label}
display={formRowDisplay}
>
<EuiDualRange
compressed={compressed}
fullWidth={fullWidth}
value={this.state.value}
onChange={this._onChange}
minInputProps={{
'aria-label': i18n.translate('kibana-react.dualRangeControl.minInputAriaLabel', {
defaultMessage: 'Range minimum',
}),
}}
maxInputProps={{
'aria-label': i18n.translate('kibana-react.dualRangeControl.maxInputAriaLabel', {
defaultMessage: 'Range maximum',
}),
}}
{...rest}
/>
</EuiFormRow>
</div>
);
}
}

View file

@ -27,7 +27,7 @@ export class LegacyManagementAdapter {
'management',
{
display: i18n.translate('management.displayName', {
defaultMessage: 'Management',
defaultMessage: 'Stack Management',
}),
},
capabilities

View file

@ -64,7 +64,7 @@ export class ManagementApp {
coreStart.chrome.setBreadcrumbs([
{
text: i18n.translate('management.breadcrumb', {
defaultMessage: 'Management',
defaultMessage: 'Stack Management',
}),
href: '#/management',
},

View file

@ -36,8 +36,8 @@ export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart
) {
home.featureCatalogue.register({
id: 'stack-management',
title: i18n.translate('management.displayName', {
defaultMessage: 'Management',
title: i18n.translate('management.stackManagement.managementLabel', {
defaultMessage: 'Stack Management',
}),
description: i18n.translate('management.stackManagement.managementDescription', {
defaultMessage: 'Your center console for managing the Elastic Stack.',

View file

@ -2,11 +2,7 @@
exports[`Flyout conflicts should allow conflict resolution 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader
@ -321,11 +317,7 @@ exports[`Flyout errors should display unsupported type errors properly 1`] = `
exports[`Flyout legacy conflicts should allow conflict resolution 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader
@ -586,11 +578,7 @@ Array [
exports[`Flyout should render import step 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader

View file

@ -2,12 +2,7 @@
exports[`Relationships should render dashboards normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -166,12 +161,7 @@ exports[`Relationships should render dashboards normally 1`] = `
exports[`Relationships should render errors 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -215,12 +205,7 @@ exports[`Relationships should render errors 1`] = `
exports[`Relationships should render index patterns normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -384,12 +369,7 @@ exports[`Relationships should render index patterns normally 1`] = `
exports[`Relationships should render searches normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -553,12 +533,7 @@ exports[`Relationships should render searches normally 1`] = `
exports[`Relationships should render visualizations normally 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}

View file

@ -3,12 +3,9 @@
exports[`OptInDetailsComponent renders as expected 1`] = `
<EuiPortal>
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={true}
onClose={[MockFunction]}
ownFocus={true}
size="m"
>
<EuiFlyoutHeader>
<EuiTitle>

View file

@ -17,6 +17,8 @@
* under the License.
*/
// @ts-ignore
import React from 'react';
import { Action, ActionContext as Context, ActionDefinition } from './action';
import { Presentable } from '../util/presentable';
import { uiToReactComponent } from '../../../kibana_react/public';

View file

@ -17,7 +17,6 @@ exports[`VisualizationNoResults should render according to snapshot 1`] = `
class="euiTextColor euiTextColor--subdued"
>
<div
aria-hidden="true"
color="subdued"
data-euiicon-type="visualizeApp"
/>

View file

@ -11,7 +11,6 @@ exports[`VisualizationRequestError should render according to snapshot 1`] = `
class="euiTextColor euiTextColor--subdued"
>
<div
aria-hidden="true"
color="danger"
data-euiicon-type="alert"
/>

View file

@ -55,7 +55,7 @@ describe('<VisualizationChart/>', () => {
it('should render initial html', () => {
const wrapper = render(<VisualizationChart vis={vis} listenOnChange={true} />);
expect(wrapper.text()).toBe('Test Visualization visualization, not yet accessible');
expect(wrapper.text()).toBe('');
});
it('should render visualization', async () => {

View file

@ -77,14 +77,7 @@ class VisualizationChart extends React.Component<VisualizationChartProps> {
public render() {
return (
<div className="visChart__container kbn-resetFocusState" tabIndex={0} ref={this.containerDiv}>
<p className="euiScreenReaderOnly">
{this.props.vis.type.title} visualization, not yet accessible
</p>
<div
aria-hidden={!this.props.vis.type.isAccessible}
className="visChart"
ref={this.chartDiv}
/>
<div className="visChart" ref={this.chartDiv} />
</div>
);
}

View file

@ -33,7 +33,7 @@ export class VisualizationNoResults extends React.Component<VisualizationNoResul
<div className="item top" />
<div className="item">
<EuiText size="xs" color="subdued">
<EuiIcon type="visualizeApp" size="m" color="subdued" aria-hidden="true" />
<EuiIcon type="visualizeApp" size="m" color="subdued" />
<EuiSpacer size="s" />

View file

@ -36,7 +36,7 @@ export class VisualizationRequestError extends React.Component<VisualizationRequ
return (
<div className="visError" ref={this.containerDiv}>
<EuiText size="xs" color="subdued">
<EuiIcon type="alert" size="m" color="danger" aria-hidden="true" />
<EuiIcon type="alert" size="m" color="danger" />
<EuiSpacer size="s" />

View file

@ -288,7 +288,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -319,7 +318,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -351,7 +349,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -602,7 +599,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -633,7 +629,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -665,7 +660,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -856,7 +850,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -887,7 +880,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -919,7 +911,6 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -1304,13 +1295,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
>
<VisTypeIcon>
<EuiIcon
aria-hidden="true"
color="secondary"
size="l"
type="empty"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
size="l"
@ -1373,13 +1362,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
>
<VisTypeIcon>
<EuiIcon
aria-hidden="true"
color="secondary"
size="l"
type="empty"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
size="l"
@ -1442,13 +1429,11 @@ exports[`NewVisModal filter for visualization types should render as expected 1`
>
<VisTypeIcon>
<EuiIcon
aria-hidden="true"
color="secondary"
size="l"
type="empty"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
size="l"
@ -1811,7 +1796,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -1860,7 +1844,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -1891,7 +1874,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2108,7 +2090,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2157,7 +2138,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2188,7 +2168,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2345,7 +2324,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2394,7 +2372,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2425,7 +2402,6 @@ exports[`NewVisModal should render as expected 1`] = `
class="euiKeyPadMenuItem__icon"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
/>
@ -2712,13 +2688,11 @@ exports[`NewVisModal should render as expected 1`] = `
>
<VisTypeIcon>
<EuiIcon
aria-hidden="true"
color="secondary"
size="l"
type="empty"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
size="l"
@ -2828,13 +2802,11 @@ exports[`NewVisModal should render as expected 1`] = `
>
<VisTypeIcon>
<EuiIcon
aria-hidden="true"
color="secondary"
size="l"
type="empty"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
size="l"
@ -2897,13 +2869,11 @@ exports[`NewVisModal should render as expected 1`] = `
>
<VisTypeIcon>
<EuiIcon
aria-hidden="true"
color="secondary"
size="l"
type="empty"
>
<div
aria-hidden="true"
color="secondary"
data-euiicon-type="empty"
size="l"

View file

@ -34,16 +34,8 @@ interface VisTypeIconProps {
export const VisTypeIcon = ({ icon, image }: VisTypeIconProps) => {
return (
<React.Fragment>
{image && (
<img
src={image}
aria-hidden="true"
role="presentation"
alt=""
className="visNewVisDialog__typeImage"
/>
)}
{!image && <EuiIcon type={icon || 'empty'} size="l" color="secondary" aria-hidden="true" />}
{image && <img src={image} alt="" className="visNewVisDialog__typeImage" />}
{!image && <EuiIcon type={icon || 'empty'} size="l" color="secondary" />}
</React.Fragment>
);
};

View file

@ -162,11 +162,5 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.common.clickConfirmOnModal();
await listingTable.searchForItemWithName('');
});
// Blocked by https://github.com/elastic/kibana/issues/38980
it.skip('Open flight dashboard', async () => {
await testSubjects.click('dashboardListingTitleLink-[Flights]-Global-Flight-Dashboard');
await a11y.testAppSnapshot();
});
});
}

View file

@ -22,7 +22,7 @@ import expect from '@kbn/expect';
const dashboardName = 'Dashboard Test Time';
export default function({ getPageObjects, getService }) {
const PageObjects = getPageObjects(['dashboard', 'header', 'timePicker']);
const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'timePicker']);
const browser = getService('browser');
describe('dashboard time', () => {
@ -32,7 +32,7 @@ export default function({ getPageObjects, getService }) {
});
after(async function() {
await PageObjects.dashboard.gotoDashboardLandingPage();
await PageObjects.common.navigateToApp('dashboard');
});
describe('dashboard without stored timed', () => {

View file

@ -97,12 +97,12 @@ export default function({ getService, getPageObjects }) {
await PageObjects.discover.brushHistogram();
const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
expect(Math.round(newDurationHours)).to.be(25);
expect(Math.round(newDurationHours)).to.be(24);
const rowData = await PageObjects.discover.getDocTableField(1);
log.debug(`The first timestamp value in doc table: ${rowData}`);
expect(Date.parse(rowData)).to.be.within(
Date.parse('Sep 20, 2015 @ 21:30:00.000'),
Date.parse('Sep 20, 2015 @ 23:00:00.000')
Date.parse('Sep 20, 2015 @ 17:30:00.000'),
Date.parse('Sep 20, 2015 @ 23:30:00.000')
);
});

View file

@ -129,29 +129,28 @@ export default function({ getService, getPageObjects }) {
expect(enabled).to.be(false);
});
// See https://github.com/elastic/kibana/issues/13137 if this test starts failing intermittently
it('Fit data bounds should zoom to level 3', async function() {
const expectedPrecision2DataTable = [
['-', 'dn', '1,429', { lat: 36, lon: -85 }],
['-', 'dp', '1,418', { lat: 41, lon: -85 }],
['-', '9y', '1,215', { lat: 36, lon: -96 }],
['-', '9z', '1,099', { lat: 42, lon: -96 }],
['-', 'dr', '1,076', { lat: 42, lon: -74 }],
['-', 'dj', '982', { lat: 31, lon: -85 }],
['-', '9v', '938', { lat: 31, lon: -96 }],
['-', '9q', '722', { lat: 36, lon: -120 }],
['-', '9w', '475', { lat: 36, lon: -107 }],
['-', 'cb', '457', { lat: 46, lon: -96 }],
['-', 'c2', '453', { lat: 47, lon: -120 }],
['-', '9x', '420', { lat: 41, lon: -107 }],
['-', 'dq', '399', { lat: 37, lon: -78 }],
['-', '9r', '396', { lat: 41, lon: -120 }],
['-', '9t', '274', { lat: 32, lon: -107 }],
['-', 'c8', '271', { lat: 47, lon: -107 }],
['-', 'dh', '214', { lat: 26, lon: -82 }],
['-', 'b6', '207', { lat: 60, lon: -162 }],
['-', 'bd', '206', { lat: 59, lon: -153 }],
['-', 'b7', '167', { lat: 64, lon: -163 }],
['-', 'dr4', '127', { lat: 40, lon: -76 }],
['-', 'dr7', '92', { lat: 41, lon: -74 }],
['-', '9q5', '91', { lat: 34, lon: -119 }],
['-', '9qc', '89', { lat: 38, lon: -122 }],
['-', 'drk', '87', { lat: 41, lon: -73 }],
['-', 'dps', '82', { lat: 42, lon: -84 }],
['-', 'dph', '82', { lat: 40, lon: -84 }],
['-', 'dp3', '79', { lat: 41, lon: -88 }],
['-', 'dpe', '78', { lat: 42, lon: -86 }],
['-', 'dp8', '77', { lat: 43, lon: -90 }],
['-', 'dp6', '74', { lat: 41, lon: -87 }],
['-', 'djv', '74', { lat: 33, lon: -83 }],
['-', '9qh', '74', { lat: 34, lon: -118 }],
['-', 'dpq', '73', { lat: 41, lon: -81 }],
['-', 'dpp', '73', { lat: 40, lon: -80 }],
['-', '9y7', '73', { lat: 35, lon: -97 }],
['-', '9vg', '73', { lat: 32, lon: -97 }],
['-', 'drs', '71', { lat: 42, lon: -73 }],
['-', '9ys', '71', { lat: 37, lon: -95 }],
['-', '9yn', '71', { lat: 34, lon: -93 }],
];
await PageObjects.tileMap.clickMapFitDataBounds();

View file

@ -32,13 +32,13 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
class HeaderPage {
public async clickDiscover() {
await appsMenu.clickLink('Discover');
await appsMenu.clickLink('Discover', { category: 'kibana' });
await PageObjects.common.waitForTopNavToBeVisible();
await this.awaitGlobalLoadingIndicatorHidden();
}
public async clickVisualize() {
await appsMenu.clickLink('Visualize');
await appsMenu.clickLink('Visualize', { category: 'kibana' });
await this.awaitGlobalLoadingIndicatorHidden();
await retry.waitFor('first breadcrumb to be "Visualize"', async () => {
const firstBreadcrumb = await globalNav.getFirstBreadcrumb();
@ -52,7 +52,7 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
}
public async clickDashboard() {
await appsMenu.clickLink('Dashboard');
await appsMenu.clickLink('Dashboard', { category: 'kibana' });
await retry.waitFor('dashboard app to be loaded', async () => {
const isNavVisible = await testSubjects.exists('top-nav');
const isLandingPageVisible = await testSubjects.exists('dashboardLandingPage');
@ -62,7 +62,7 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo
}
public async clickStackManagement() {
await appsMenu.clickLink('Management');
await appsMenu.clickLink('Stack Management', { category: 'management' });
await this.awaitGlobalLoadingIndicatorHidden();
}

View file

@ -22,16 +22,34 @@ import { FtrProviderContext } from '../ftr_provider_context';
export function AppsMenuProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const log = getService('log');
const find = getService('find');
return new (class AppsMenu {
/**
* Close the collapsible nav
* TODO #64541 can replace with a data-test-subj
*/
public async closeCollapsibleNav() {
const CLOSE_BUTTON = '[data-test-subj=collapsibleNav] > button';
if (await find.existsByCssSelector(CLOSE_BUTTON)) {
(await find.byCssSelector(CLOSE_BUTTON)).click();
}
}
public async openCollapsibleNav() {
if (!(await testSubjects.exists('collapsibleNav'))) {
await testSubjects.click('toggleNavButton');
}
}
/**
* Get the attributes from each of the links in the apps menu
*/
public async readLinks() {
const appMenu = await testSubjects.find('navDrawer');
await this.openCollapsibleNav();
const appMenu = await testSubjects.find('collapsibleNav');
const $ = await appMenu.parseDomContent();
const links = $.findTestSubjects('navDrawerAppsMenuLink')
const links = $.findTestSubjects('collapsibleNavAppLink')
.toArray()
.map(link => {
return {
@ -41,6 +59,8 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) {
};
});
await this.closeCollapsibleNav();
return links;
}
@ -63,14 +83,32 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) {
/**
* Click the app link within the app menu that has the given name
* @param name
* @param options.closeCollapsibleNav
* @param options.category - optional field to ensure that a link is clicked in a particular category
* helpful when there may be a recent link with the same name as an app
*/
public async clickLink(name: string) {
public async clickLink(
name: string,
{
closeCollapsibleNav = true,
category,
}: { closeCollapsibleNav?: boolean; category?: string } = {}
) {
try {
log.debug(`click "${name}" app link`);
const container = await testSubjects.find('navDrawer');
// Text content is not visible or selectable (0px width) so we use an attr with th same value
const link = await container.findByCssSelector(`[aria-label='${name}']`);
await this.openCollapsibleNav();
let nav;
if (typeof category === 'string') {
nav = await testSubjects.find(`collapsibleNavGroup-${category}`);
} else {
nav = await testSubjects.find('collapsibleNav');
}
const link = await nav.findByPartialLinkText(name);
await link.click();
if (closeCollapsibleNav) {
await this.closeCollapsibleNav();
}
} finally {
// Intentionally empty
}

View file

@ -7,7 +7,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"react": "^16.12.0",
"react-dom": "^16.12.0"
}

View file

@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"react": "^16.12.0"
},
"scripts": {

View file

@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"react": "^16.12.0"
},
"scripts": {

View file

@ -8,7 +8,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"react": "^16.12.0"
},
"scripts": {

View file

@ -59,19 +59,19 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
describe('when navigating to a legacy app', () => {
it('prevents navigation if user click cancel on the alert dialog', async () => {
await PageObjects.common.navigateToApp('appleave1');
await appsMenu.clickLink('Core Legacy Compat');
await appsMenu.clickLink('Core Legacy Compat', { closeCollapsibleNav: false });
const alert = await browser.getAlert();
expect(alert).not.to.eql(undefined);
expect(alert).not.to.eql(null);
alert!.dismiss();
expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/appleave1'));
});
it('allows navigation if user click leave on the alert dialog', async () => {
await PageObjects.common.navigateToApp('appleave1');
await appsMenu.clickLink('Core Legacy Compat');
await appsMenu.clickLink('Core Legacy Compat', { closeCollapsibleNav: false });
const alert = await browser.getAlert();
expect(alert).not.to.eql(undefined);
expect(alert).not.to.eql(null);
alert!.accept();
expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/core_legacy_compat'));
});

View file

@ -75,14 +75,6 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
});
link = await appsMenu.getLink('App Status');
expect(link).to.eql(undefined);
await setAppStatus({
navLinkStatus: AppNavLinkStatus.visible,
tooltip: 'Some tooltip',
});
link = await appsMenu.getLink('Some tooltip'); // the tooltip replaces the name in the selector we use.
expect(link).not.to.eql(undefined);
expect(link!.disabled).to.eql(false);
});
it('shows an error when navigating to an inaccessible app', async () => {

View file

@ -33,11 +33,9 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
const loadingScreenNotShown = async () =>
expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false);
const loadingScreenShown = () => testSubjects.existOrFail('kbnLoadingMessage');
const getAppWrapperWidth = async () => {
const getAppWrapperHeight = async () => {
const wrapper = await find.byClassName('app-wrapper');
return (await wrapper.getSize()).width;
return (await wrapper.getSize()).height;
};
const getKibanaUrl = (pathname?: string, search?: string) =>
@ -90,7 +88,7 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
it('navigates to app root when navlink is clicked', async () => {
await appsMenu.clickLink('Foo');
await waitForUrlToBe('/app/foo');
await loadingScreenNotShown();
// await loadingScreenNotShown();
await testSubjects.existOrFail('fooAppHome');
});
@ -122,9 +120,9 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
await loadingScreenNotShown();
expect(await testSubjects.exists('headerGlobalNav')).to.be(false);
const wrapperWidth = await getAppWrapperWidth();
const windowWidth = (await browser.getWindowSize()).width;
expect(wrapperWidth).to.eql(windowWidth);
const wrapperHeight = await getAppWrapperHeight();
const windowHeight = (await browser.getWindowSize()).height;
expect(wrapperHeight).to.eql(windowHeight);
});
it('navigating away from chromeless application shows chrome', async () => {
@ -132,20 +130,18 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider
await loadingScreenNotShown();
expect(await testSubjects.exists('headerGlobalNav')).to.be(true);
const wrapperWidth = await getAppWrapperWidth();
const windowWidth = (await browser.getWindowSize()).width;
expect(wrapperWidth).to.be.below(windowWidth);
const wrapperHeight = await getAppWrapperHeight();
const windowHeight = (await browser.getWindowSize()).height;
expect(wrapperHeight).to.be.below(windowHeight);
});
it('can navigate from NP apps to legacy apps', async () => {
await appsMenu.clickLink('Management');
await loadingScreenShown();
await appsMenu.clickLink('Stack Management');
await testSubjects.existOrFail('managementNav');
});
it('can navigate from legacy apps to NP apps', async () => {
await appsMenu.clickLink('Foo');
await loadingScreenShown();
await testSubjects.existOrFail('fooAppHome');
});
});

View file

@ -61,7 +61,7 @@ export function dashboardMode(kibana) {
title: i18n.translate('xpack.dashboardMode.dashboardViewer.dashboardTitle', {
defaultMessage: 'Dashboard',
}),
order: -1001,
order: 1000,
url: `${kbnBaseUrl}#/dashboards`,
subUrlBase: `${kbnBaseUrl}#/dashboard`,
description: i18n.translate(
@ -71,7 +71,7 @@ export function dashboardMode(kibana) {
}
),
icon: 'plugins/kibana/dashboard/assets/dashboard.svg',
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
},
],
},

View file

@ -34,7 +34,8 @@ export function maps(kibana) {
main: 'plugins/maps/legacy',
icon: 'plugins/maps/icon.svg',
euiIconType: APP_ICON,
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
order: 4000,
},
injectDefaultVars(server) {
const serverConfig = server.config();

View file

@ -189,7 +189,7 @@
"@elastic/apm-rum-react": "^1.1.1",
"@elastic/datemath": "5.0.3",
"@elastic/ems-client": "7.8.0",
"@elastic/eui": "22.3.0",
"@elastic/eui": "22.3.1",
"@elastic/filesaver": "1.1.2",
"@elastic/maki": "6.3.0",
"@elastic/node-crypto": "1.1.1",

View file

@ -5,6 +5,8 @@ body.canvas-isFullscreen { // sass-lint:disable-line no-qualifying-elements
}
// remove space for global nav elements
// TODO #64541
// Can delete this block
.chrHeaderWrapper ~ .app-wrapper {
// Override locked nav at all breakpoints
left: 0 !important; // sass-lint:disable-line no-important

View file

@ -69,7 +69,7 @@ export class CanvasPlugin
this.srcPlugin.setup(core, { canvas: canvasApi });
core.application.register({
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
id: 'canvas',
title: 'Canvas',
euiIconType: 'canvasApp',

View file

@ -33,7 +33,7 @@ storiesOf('components/FlyoutFrame', module)
})
.add('open in flyout', () => {
return (
<EuiFlyout>
<EuiFlyout onClose={() => {}}>
<FlyoutFrame
title="Create drilldown"
footer={<EuiButton>Save</EuiButton>}

View file

@ -66,10 +66,10 @@ export class GraphPlugin
core.application.register({
id: 'graph',
title: 'Graph',
order: 9000,
order: 6000,
appRoute: '/app/graph',
euiIconType: 'graphApp',
category: DEFAULT_APP_CATEGORIES.analyze,
category: DEFAULT_APP_CATEGORIES.kibana,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
const { renderApp } = await import('./application');

View file

@ -64,7 +64,7 @@ export class Plugin
defaultMessage: 'Logs',
}),
euiIconType: 'logsApp',
order: 8001,
order: 8000,
appRoute: '/app/logs',
category: DEFAULT_APP_CATEGORIES.observability,
mount: async (params: AppMountParameters) => {
@ -89,7 +89,7 @@ export class Plugin
defaultMessage: 'Metrics',
}),
euiIconType: 'metricsApp',
order: 8000,
order: 8001,
appRoute: '/app/metrics',
category: DEFAULT_APP_CATEGORIES.observability,
mount: async (params: AppMountParameters) => {

View file

@ -4,7 +4,7 @@
#maps-plugin {
display: flex;
flex-direction: column;
height: calc(100vh - #{$euiHeaderChildSize});
height: calc(100vh - #{$euiHeaderHeightCompensation});
width: 100%;
overflow: hidden;
}

View file

@ -9,12 +9,7 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule
<EuiFlyout
aria-labelledby="flyoutTitle"
className="ml-rule-editor-flyout"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -259,12 +254,7 @@ exports[`RuleEditorFlyout renders the flyout after setting the rule to edit 1`]
<EuiFlyout
aria-labelledby="flyoutTitle"
className="ml-rule-editor-flyout"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -523,12 +513,7 @@ exports[`RuleEditorFlyout renders the flyout for creating a rule with conditions
<EuiFlyout
aria-labelledby="flyoutTitle"
className="ml-rule-editor-flyout"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -765,12 +750,7 @@ exports[`RuleEditorFlyout renders the select action component for a detector wit
<EuiFlyout
aria-labelledby="flyoutTitle"
className="ml-rule-editor-flyout"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}

View file

@ -20,6 +20,7 @@ import { LicenseManagementUIPluginSetup } from '../../license_management/public'
import { setDependencyCache } from './application/util/dependency_cache';
import { PLUGIN_ID, PLUGIN_ICON } from '../common/constants/app';
import { registerFeature } from './register_feature';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { registerEmbeddables } from './embeddables';
import { UiActionsSetup } from '../../../../src/plugins/ui_actions/public';
import { registerMlUiActions } from './ui_actions';
@ -46,9 +47,10 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
title: i18n.translate('xpack.ml.plugin.title', {
defaultMessage: 'Machine Learning',
}),
order: 30,
order: 5000,
euiIconType: PLUGIN_ICON,
appRoute: '/app/ml',
category: DEFAULT_APP_CATEGORIES.kibana,
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
const { renderApp } = await import('./application/app');

View file

@ -3,12 +3,7 @@
exports[`Status should render a flyout when clicking the link 1`] = `
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}

View file

@ -2,11 +2,7 @@
exports[`DetailDrawer component If vertices shows basic info and no stats for if 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader>
@ -61,11 +57,7 @@ exports[`DetailDrawer component If vertices shows basic info and no stats for if
exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID shows basic info and stats for plugin, suggesting that user set explicit ID 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader>
@ -318,11 +310,7 @@ exports[`DetailDrawer component Plugin vertices Plugin does not have explicit ID
exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows basic info and stats for plugin, including explicit ID 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader>
@ -569,11 +557,7 @@ exports[`DetailDrawer component Plugin vertices Plugin has explicit ID shows bas
exports[`DetailDrawer component Queue vertices shows basic info and no stats for queue 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader>
@ -620,11 +604,7 @@ exports[`DetailDrawer component Queue vertices shows basic info and no stats for
exports[`DetailDrawer component shows vertex title 1`] = `
<EuiFlyout
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[MockFunction]}
ownFocus={false}
size="s"
>
<EuiFlyoutHeader>

View file

@ -3,12 +3,7 @@
exports[`Flyout apm part one should render normally 1`] = `
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -342,12 +337,7 @@ exports[`Flyout apm part two should show instructions to migrate to metricbeat 1
exports[`Flyout beats part one should render normally 1`] = `
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -723,12 +713,7 @@ exports[`Flyout beats part two should show instructions to migrate to metricbeat
exports[`Flyout elasticsearch part one should render normally 1`] = `
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -1064,12 +1049,7 @@ exports[`Flyout elasticsearch part two should show instructions to migrate to me
exports[`Flyout kibana part one should render normally 1`] = `
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}
@ -1412,12 +1392,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea
exports[`Flyout logstash part one should render normally 1`] = `
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={false}
size="m"
>
<EuiFlyoutHeader
hasBorder={true}

View file

@ -7,6 +7,7 @@
import React, { useState, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { NavType } from 'src/core/public';
import { formatRequestPayload, formatJson } from '../lib/format';
import { exampleScript } from '../constants';
import { PayloadFormat } from '../types';
@ -23,7 +24,7 @@ export const Main: React.FunctionComponent = () => {
updatePayload,
services: {
http,
chrome: { getIsNavDrawerLocked$ },
chrome: { getIsNavDrawerLocked$, getNavType$ },
},
links,
} = useAppContext();
@ -43,6 +44,7 @@ export const Main: React.FunctionComponent = () => {
};
const [isNavDrawerLocked, setIsNavDrawerLocked] = useState(false);
const [isNavLegacy, setIsNavLegacy] = useState(false);
useEffect(() => {
const subscription = getIsNavDrawerLocked$().subscribe((newIsNavDrawerLocked: boolean) => {
@ -52,6 +54,14 @@ export const Main: React.FunctionComponent = () => {
return () => subscription.unsubscribe();
});
useEffect(() => {
const subscription = getNavType$().subscribe((navType: NavType) => {
setIsNavLegacy(navType === 'legacy');
});
return () => subscription.unsubscribe();
});
return (
<div className="painlessLabMainContainer">
<EuiFlexGroup className="painlessLabPanelsContainer" responsive={false} gutterSize="none">
@ -78,6 +88,7 @@ export const Main: React.FunctionComponent = () => {
toggleRequestFlyout={toggleRequestFlyout}
isRequestFlyoutOpen={isRequestFlyoutOpen}
isNavDrawerLocked={isNavDrawerLocked}
isNavLegacy={isNavLegacy}
reset={() => updatePayload({ code: exampleScript })}
/>

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useState } from 'react';
import classNames from 'classnames';
import {
EuiPopover,
EuiBottomBar,
@ -15,7 +14,7 @@ import {
EuiButtonEmpty,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import classNames from 'classnames';
import { Links } from '../../links';
interface Props {
@ -25,6 +24,7 @@ interface Props {
reset: () => void;
links: Links;
isNavDrawerLocked: boolean;
isNavLegacy: boolean;
}
export function MainControls({
@ -33,6 +33,7 @@ export function MainControls({
reset,
links,
isNavDrawerLocked,
isNavLegacy,
}: Props) {
const [isHelpOpen, setIsHelpOpen] = useState(false);
@ -87,9 +88,14 @@ export function MainControls({
</EuiContextMenuItem>,
];
const classes = classNames('painlessLab__bottomBar', {
'painlessLab__bottomBar-isNavDrawerLocked': isNavDrawerLocked,
});
// TODO #64541
// Can delete all this class stuff
let classes = '';
if (isNavLegacy) {
classes = classNames('painlessLab__bottomBar', {
'painlessLab__bottomBar-isNavDrawerLocked': isNavDrawerLocked,
});
}
return (
<EuiBottomBar paddingSize="s" className={classes}>
@ -116,7 +122,7 @@ export function MainControls({
closePopover={() => setIsHelpOpen(false)}
panelPaddingSize="none"
withTitle
anchorPosition="upRight"
anchorPosition="upLeft"
>
<EuiContextMenuPanel items={items} />
</EuiPopover>

View file

@ -8,7 +8,7 @@
$bottomBarHeight: calc(#{$euiSize} * 3);
.painlessLabBottomBarPlaceholder {
height: $bottomBarHeight
height: $bottomBarHeight;
}
.painlessLabLeftPane {

View file

@ -28,10 +28,7 @@ exports[`ReportInfoButton opens flyout with fetch error info 1`] = `
Array [
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
data-test-subj="reportInfoFlyout"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={true}
size="s"
@ -136,7 +133,7 @@ Array [
tabindex="0"
>
<button
aria-label="Closes this dialog"
aria-label="Close this dialog"
class="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
type="button"
@ -196,7 +193,7 @@ Array [
tabindex="0"
>
<button
aria-label="Closes this dialog"
aria-label="Close this dialog"
class="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
type="button"
@ -249,36 +246,41 @@ Array [
role="dialog"
tabIndex={0}
>
<EuiButtonIcon
aria-label="Closes this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
<EuiI18n
default="Close this dialog"
token="euiFlyout.closeAriaLabel"
>
<button
aria-label="Closes this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
<EuiButtonIcon
aria-label="Close this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="cross"
<button
aria-label="Close this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
onClick={[Function]}
type="button"
>
<div
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
type="cross"
>
<div
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
</EuiI18n>
<EuiFlyoutHeader
hasBorder={true}
>
@ -348,36 +350,41 @@ Array [
role="dialog"
tabIndex={0}
>
<EuiButtonIcon
aria-label="Closes this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
<EuiI18n
default="Close this dialog"
token="euiFlyout.closeAriaLabel"
>
<button
aria-label="Closes this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
<EuiButtonIcon
aria-label="Close this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="cross"
<button
aria-label="Close this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
onClick={[Function]}
type="button"
>
<div
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
type="cross"
>
<div
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
</EuiI18n>
<EuiFlyoutHeader
hasBorder={true}
>
@ -425,10 +432,7 @@ exports[`ReportInfoButton opens flyout with info 1`] = `
Array [
<EuiFlyout
aria-labelledby="flyoutTitle"
closeButtonAriaLabel="Closes this dialog"
data-test-subj="reportInfoFlyout"
hideCloseButton={false}
maxWidth={false}
onClose={[Function]}
ownFocus={true}
size="s"
@ -533,7 +537,7 @@ Array [
tabindex="0"
>
<button
aria-label="Closes this dialog"
aria-label="Close this dialog"
class="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
type="button"
@ -591,7 +595,7 @@ Array [
tabindex="0"
>
<button
aria-label="Closes this dialog"
aria-label="Close this dialog"
class="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
type="button"
@ -642,36 +646,41 @@ Array [
role="dialog"
tabIndex={0}
>
<EuiButtonIcon
aria-label="Closes this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
<EuiI18n
default="Close this dialog"
token="euiFlyout.closeAriaLabel"
>
<button
aria-label="Closes this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
<EuiButtonIcon
aria-label="Close this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="cross"
<button
aria-label="Close this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
onClick={[Function]}
type="button"
>
<div
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
type="cross"
>
<div
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
</EuiI18n>
<EuiFlyoutHeader
hasBorder={true}
>
@ -739,36 +748,41 @@ Array [
role="dialog"
tabIndex={0}
>
<EuiButtonIcon
aria-label="Closes this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
<EuiI18n
default="Close this dialog"
token="euiFlyout.closeAriaLabel"
>
<button
aria-label="Closes this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
<EuiButtonIcon
aria-label="Close this dialog"
className="euiFlyout__closeButton"
color="text"
data-test-subj="euiFlyoutCloseButton"
iconType="cross"
onClick={[Function]}
type="button"
>
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
size="m"
type="cross"
<button
aria-label="Close this dialog"
className="euiButtonIcon euiButtonIcon--text euiFlyout__closeButton"
data-test-subj="euiFlyoutCloseButton"
onClick={[Function]}
type="button"
>
<div
<EuiIcon
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
type="cross"
>
<div
aria-hidden="true"
className="euiButtonIcon__icon"
data-euiicon-type="cross"
size="m"
/>
</EuiIcon>
</button>
</EuiButtonIcon>
</EuiI18n>
<EuiFlyoutHeader
hasBorder={true}
>

View file

@ -151,7 +151,7 @@ export class FieldRuleEditor extends Component<Props, {}> {
)}
/>
) : (
<EuiIcon size="l" type="empty" aria-hidden={true} />
<EuiIcon size="l" type="empty" />
)}
</EuiFormRow>
</EuiFlexItem>

View file

@ -22,7 +22,7 @@ describe('SecurityNavControl', () => {
const wrapper = shallowWithIntl(<SecurityNavControl {...props} />);
const { button } = wrapper.find(EuiPopover).props();
expect(button).toMatchInlineSnapshot(`
<EuiHeaderSectionItemButton
<ForwardRef
aria-controls="headerUserMenu"
aria-expanded={false}
aria-haspopup="true"
@ -33,7 +33,7 @@ describe('SecurityNavControl', () => {
<EuiLoadingSpinner
size="m"
/>
</EuiHeaderSectionItemButton>
</ForwardRef>
`);
});
@ -49,7 +49,7 @@ describe('SecurityNavControl', () => {
wrapper.update();
const { button } = wrapper.find(EuiPopover).props();
expect(button).toMatchInlineSnapshot(`
<EuiHeaderSectionItemButton
<ForwardRef
aria-controls="headerUserMenu"
aria-expanded={false}
aria-haspopup="true"
@ -61,7 +61,7 @@ describe('SecurityNavControl', () => {
name="foo"
size="s"
/>
</EuiHeaderSectionItemButton>
</ForwardRef>
`);
});

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