mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Serverless] Make project header component handle chrome visibility changes (#162746)
## Summary Closes https://github.com/elastic/kibana/issues/160834 This PR fixes a bug with the Dashboard app in serverless projects. The Dashboard has a "Full screen" button that is intended to cause the content area of the dashboard take up the entire viewport. To do this, the dashboard app uses a chrome service to update an observable used in the rendering of the header, which sets the layout to a "chromeless" state. The bug is: in serverless, the project header must respect the chromeless state. ### Testing 1. Run `yarn es snapshot` in one terminal and then `yarn serverless` in another terminal. 2. Load sample data through the "Integrations" app, which can be found in Global Search. 3. View a sample data dashboard, and use the `Full screen` button in the app menu toolbar. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Christiane (Tina) Heiligers <christiane.heiligers@elastic.co>
This commit is contained in:
parent
d8078b625d
commit
2c6fd26f5e
5 changed files with 119 additions and 105 deletions
|
@ -73,8 +73,12 @@ const createHistoryMock = (): jest.Mocked<History> => {
|
|||
};
|
||||
};
|
||||
|
||||
const createInternalStartContractMock = (): jest.Mocked<InternalApplicationStart> => {
|
||||
const currentAppId$ = new Subject<string | undefined>();
|
||||
const createInternalStartContractMock = (
|
||||
currentAppId?: string
|
||||
): jest.Mocked<InternalApplicationStart> => {
|
||||
const currentAppId$ = currentAppId
|
||||
? new BehaviorSubject<string | undefined>(currentAppId)
|
||||
: new Subject<string | undefined>();
|
||||
|
||||
return {
|
||||
applications$: new BehaviorSubject<Map<string, PublicAppInfo>>(new Map()),
|
||||
|
|
|
@ -45,9 +45,9 @@ Object.defineProperty(window, 'localStorage', {
|
|||
writable: true,
|
||||
});
|
||||
|
||||
function defaultStartDeps(availableApps?: App[]) {
|
||||
function defaultStartDeps(availableApps?: App[], currentAppId?: string) {
|
||||
const deps = {
|
||||
application: applicationServiceMock.createInternalStartContract(),
|
||||
application: applicationServiceMock.createInternalStartContract(currentAppId),
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
http: httpServiceMock.createStartContract(),
|
||||
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
|
||||
|
@ -192,7 +192,7 @@ describe('start', () => {
|
|||
expect(startDeps.notifications.toasts.addWarning).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('getComponent', () => {
|
||||
describe('getHeaderComponent', () => {
|
||||
it('returns a renderable React component', async () => {
|
||||
const { chrome } = await start();
|
||||
|
||||
|
@ -202,7 +202,9 @@ describe('start', () => {
|
|||
});
|
||||
|
||||
it('renders the default project side navigation', async () => {
|
||||
const { chrome } = await start();
|
||||
const { chrome } = await start({
|
||||
startDeps: defaultStartDeps([{ id: 'foo', title: 'Foo' } as App], 'foo'),
|
||||
});
|
||||
|
||||
chrome.setChromeStyle('project');
|
||||
|
||||
|
@ -216,7 +218,9 @@ describe('start', () => {
|
|||
});
|
||||
|
||||
it('renders the custom project side navigation', async () => {
|
||||
const { chrome } = await start();
|
||||
const { chrome } = await start({
|
||||
startDeps: defaultStartDeps([{ id: 'foo', title: 'Foo' } as App], 'foo'),
|
||||
});
|
||||
|
||||
const MyNav = function MyNav() {
|
||||
return <div data-test-subj="customProjectSideNav">HELLO</div>;
|
||||
|
@ -235,6 +239,17 @@ describe('start', () => {
|
|||
const customProjectSideNav = findTestSubject(component, 'customProjectSideNav');
|
||||
expect(customProjectSideNav.text()).toBe('HELLO');
|
||||
});
|
||||
|
||||
it('renders chromeless header', async () => {
|
||||
const { chrome } = await start({ startDeps: defaultStartDeps() });
|
||||
|
||||
chrome.setIsVisible(false);
|
||||
|
||||
const component = mount(chrome.getHeaderComponent());
|
||||
|
||||
const chromeless = findTestSubject(component, 'kibanaHeaderChromeless');
|
||||
expect(chromeless.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visibility', () => {
|
||||
|
|
|
@ -43,9 +43,10 @@ import { NavControlsService } from './nav_controls';
|
|||
import { NavLinksService } from './nav_links';
|
||||
import { ProjectNavigationService } from './project_navigation';
|
||||
import { RecentlyAccessedService } from './recently_accessed';
|
||||
import { Header, ProjectHeader, ProjectSideNavigation } from './ui';
|
||||
import { Header, LoadingIndicator, ProjectHeader, ProjectSideNavigation } from './ui';
|
||||
import { registerAnalyticsContextProvider } from './register_analytics_context_provider';
|
||||
import type { InternalChromeStart } from './types';
|
||||
import { HeaderTopBanner } from './ui/header/header_top_banner';
|
||||
|
||||
const IS_LOCKED_KEY = 'core.chrome.isLocked';
|
||||
const SNAPSHOT_REGEX = /-snapshot/i;
|
||||
|
@ -265,89 +266,102 @@ export class ChromeService {
|
|||
}
|
||||
|
||||
const getHeaderComponent = () => {
|
||||
if (chromeStyle$.getValue() === 'project') {
|
||||
const projectNavigationComponent$ = projectNavigation.getProjectSideNavComponent$();
|
||||
const projectBreadcrumbs$ = projectNavigation
|
||||
.getProjectBreadcrumbs$()
|
||||
.pipe(takeUntil(this.stop$));
|
||||
const activeNodes$ = projectNavigation.getActiveNodes$();
|
||||
|
||||
const ProjectHeaderWithNavigation = () => {
|
||||
const CustomSideNavComponent = useObservable(projectNavigationComponent$, undefined);
|
||||
const activeNodes = useObservable(activeNodes$, []);
|
||||
|
||||
const currentProjectBreadcrumbs$ = projectBreadcrumbs$;
|
||||
|
||||
let SideNavComponent: ISideNavComponent = () => null;
|
||||
|
||||
if (CustomSideNavComponent !== undefined) {
|
||||
// We have the state from the Observable
|
||||
SideNavComponent =
|
||||
CustomSideNavComponent.current !== null
|
||||
? CustomSideNavComponent.current
|
||||
: ProjectSideNavigation;
|
||||
}
|
||||
const HeaderComponent = () => {
|
||||
const isVisible = useObservable(this.isVisible$);
|
||||
|
||||
if (!isVisible) {
|
||||
return (
|
||||
<ProjectHeader
|
||||
{...{
|
||||
application,
|
||||
globalHelpExtensionMenuLinks$,
|
||||
}}
|
||||
actionMenu$={application.currentActionMenu$}
|
||||
breadcrumbs$={currentProjectBreadcrumbs$}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
|
||||
helpMenuLinks$={helpMenuLinks$}
|
||||
navControlsLeft$={navControls.getLeft$()}
|
||||
navControlsCenter$={navControls.getCenter$()}
|
||||
navControlsRight$={navControls.getRight$()}
|
||||
loadingCount$={http.getLoadingCount$()}
|
||||
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
|
||||
homeHref$={projectNavigation.getProjectHome$()}
|
||||
docLinks={docLinks}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
prependBasePath={http.basePath.prepend}
|
||||
>
|
||||
<SideNavComponent activeNodes={activeNodes} />
|
||||
</ProjectHeader>
|
||||
<div data-test-subj="kibanaHeaderChromeless">
|
||||
<LoadingIndicator loadingCount$={http.getLoadingCount$()} showAsBar />
|
||||
<HeaderTopBanner headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return <ProjectHeaderWithNavigation />;
|
||||
}
|
||||
// render header
|
||||
if (chromeStyle$.getValue() === 'project') {
|
||||
const projectNavigationComponent$ = projectNavigation.getProjectSideNavComponent$();
|
||||
const projectBreadcrumbs$ = projectNavigation
|
||||
.getProjectBreadcrumbs$()
|
||||
.pipe(takeUntil(this.stop$));
|
||||
const activeNodes$ = projectNavigation.getActiveNodes$();
|
||||
|
||||
return (
|
||||
<Header
|
||||
loadingCount$={http.getLoadingCount$()}
|
||||
application={application}
|
||||
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
|
||||
badge$={badge$.pipe(takeUntil(this.stop$))}
|
||||
basePath={http.basePath}
|
||||
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
|
||||
breadcrumbsAppendExtension$={breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$))}
|
||||
customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))}
|
||||
kibanaDocLink={docLinks.links.kibana.guide}
|
||||
docLinks={docLinks}
|
||||
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
|
||||
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
|
||||
helpMenuLinks$={helpMenuLinks$}
|
||||
homeHref={http.basePath.prepend('/app/home')}
|
||||
isVisible$={this.isVisible$}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
navLinks$={navLinks.getNavLinks$()}
|
||||
recentlyAccessed$={recentlyAccessed.get$()}
|
||||
navControlsLeft$={navControls.getLeft$()}
|
||||
navControlsCenter$={navControls.getCenter$()}
|
||||
navControlsRight$={navControls.getRight$()}
|
||||
navControlsExtension$={navControls.getExtension$()}
|
||||
onIsLockedUpdate={setIsNavDrawerLocked}
|
||||
isLocked$={getIsNavDrawerLocked$}
|
||||
customBranding$={customBranding$}
|
||||
/>
|
||||
);
|
||||
const ProjectHeaderWithNavigationComponent = () => {
|
||||
const CustomSideNavComponent = useObservable(projectNavigationComponent$, undefined);
|
||||
const activeNodes = useObservable(activeNodes$, []);
|
||||
|
||||
const currentProjectBreadcrumbs$ = projectBreadcrumbs$;
|
||||
|
||||
let SideNavComponent: ISideNavComponent = () => null;
|
||||
|
||||
if (CustomSideNavComponent !== undefined) {
|
||||
// We have the state from the Observable
|
||||
SideNavComponent =
|
||||
CustomSideNavComponent.current !== null
|
||||
? CustomSideNavComponent.current
|
||||
: ProjectSideNavigation;
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectHeader
|
||||
application={application}
|
||||
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
|
||||
actionMenu$={application.currentActionMenu$}
|
||||
breadcrumbs$={currentProjectBreadcrumbs$}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
|
||||
helpMenuLinks$={helpMenuLinks$}
|
||||
navControlsLeft$={navControls.getLeft$()}
|
||||
navControlsCenter$={navControls.getCenter$()}
|
||||
navControlsRight$={navControls.getRight$()}
|
||||
loadingCount$={http.getLoadingCount$()}
|
||||
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
|
||||
homeHref$={projectNavigation.getProjectHome$()}
|
||||
docLinks={docLinks}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
prependBasePath={http.basePath.prepend}
|
||||
>
|
||||
<SideNavComponent activeNodes={activeNodes} />
|
||||
</ProjectHeader>
|
||||
);
|
||||
};
|
||||
|
||||
return <ProjectHeaderWithNavigationComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Header
|
||||
loadingCount$={http.getLoadingCount$()}
|
||||
application={application}
|
||||
headerBanner$={headerBanner$.pipe(takeUntil(this.stop$))}
|
||||
badge$={badge$.pipe(takeUntil(this.stop$))}
|
||||
basePath={http.basePath}
|
||||
breadcrumbs$={breadcrumbs$.pipe(takeUntil(this.stop$))}
|
||||
breadcrumbsAppendExtension$={breadcrumbsAppendExtension$.pipe(takeUntil(this.stop$))}
|
||||
customNavLink$={customNavLink$.pipe(takeUntil(this.stop$))}
|
||||
kibanaDocLink={docLinks.links.kibana.guide}
|
||||
docLinks={docLinks}
|
||||
forceAppSwitcherNavigation$={navLinks.getForceAppSwitcherNavigation$()}
|
||||
globalHelpExtensionMenuLinks$={globalHelpExtensionMenuLinks$}
|
||||
helpExtension$={helpExtension$.pipe(takeUntil(this.stop$))}
|
||||
helpSupportUrl$={helpSupportUrl$.pipe(takeUntil(this.stop$))}
|
||||
helpMenuLinks$={helpMenuLinks$}
|
||||
homeHref={http.basePath.prepend('/app/home')}
|
||||
kibanaVersion={injectedMetadata.getKibanaVersion()}
|
||||
navLinks$={navLinks.getNavLinks$()}
|
||||
recentlyAccessed$={recentlyAccessed.get$()}
|
||||
navControlsLeft$={navControls.getLeft$()}
|
||||
navControlsCenter$={navControls.getCenter$()}
|
||||
navControlsRight$={navControls.getRight$()}
|
||||
navControlsExtension$={navControls.getExtension$()}
|
||||
onIsLockedUpdate={setIsNavDrawerLocked}
|
||||
isLocked$={getIsNavDrawerLocked$}
|
||||
customBranding$={customBranding$}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return <HeaderComponent />;
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -28,7 +28,6 @@ function mockProps() {
|
|||
breadcrumbs$: new BehaviorSubject([]),
|
||||
breadcrumbsAppendExtension$: new BehaviorSubject(undefined),
|
||||
homeHref: '/',
|
||||
isVisible$: new BehaviorSubject(true),
|
||||
customBranding$: new BehaviorSubject({}),
|
||||
kibanaDocLink: '/docs',
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
|
@ -58,7 +57,6 @@ describe('Header', () => {
|
|||
});
|
||||
|
||||
it('renders', () => {
|
||||
const isVisible$ = new BehaviorSubject(false);
|
||||
const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }]);
|
||||
const isLocked$ = new BehaviorSubject(false);
|
||||
const navLinks$ = new BehaviorSubject([
|
||||
|
@ -81,7 +79,6 @@ describe('Header', () => {
|
|||
const component = mountWithIntl(
|
||||
<Header
|
||||
{...mockProps()}
|
||||
isVisible$={isVisible$}
|
||||
breadcrumbs$={breadcrumbs$}
|
||||
navLinks$={navLinks$}
|
||||
recentlyAccessed$={recentlyAccessed$}
|
||||
|
@ -92,10 +89,6 @@ describe('Header', () => {
|
|||
helpMenuLinks$={of([])}
|
||||
/>
|
||||
);
|
||||
expect(component.find('EuiHeader').exists()).toBeFalsy();
|
||||
|
||||
act(() => isVisible$.next(true));
|
||||
component.update();
|
||||
expect(component.find('EuiHeader').exists()).toBeTruthy();
|
||||
expect(component.find('nav[aria-label="Primary"]').exists()).toBeFalsy();
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import type {
|
|||
} from '@kbn/core-chrome-browser';
|
||||
import { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { DocLinksStart } from '@kbn/core-doc-links-browser';
|
||||
import { LoadingIndicator } from '../loading_indicator';
|
||||
import type { OnIsLockedUpdate } from './types';
|
||||
import { CollapsibleNav } from './collapsible_nav';
|
||||
import { HeaderBadge } from './header_badge';
|
||||
|
@ -59,7 +58,6 @@ export interface HeaderProps {
|
|||
breadcrumbsAppendExtension$: Observable<ChromeBreadcrumbsAppendExtension | undefined>;
|
||||
customNavLink$: Observable<ChromeNavLink | undefined>;
|
||||
homeHref: string;
|
||||
isVisible$: Observable<boolean>;
|
||||
kibanaDocLink: string;
|
||||
docLinks: DocLinksStart;
|
||||
navLinks$: Observable<ChromeNavLink[]>;
|
||||
|
@ -93,21 +91,11 @@ export function Header({
|
|||
customBranding$,
|
||||
...observables
|
||||
}: HeaderProps) {
|
||||
const isVisible = useObservable(observables.isVisible$, false);
|
||||
const [isNavOpen, setIsNavOpen] = useState(false);
|
||||
const [navId] = useState(htmlIdGenerator()());
|
||||
const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$);
|
||||
const headerActionMenuMounter = useHeaderActionMenuMounter(application.currentActionMenu$);
|
||||
|
||||
if (!isVisible) {
|
||||
return (
|
||||
<>
|
||||
<LoadingIndicator loadingCount$={observables.loadingCount$} showAsBar />
|
||||
<HeaderTopBanner headerBanner$={observables.headerBanner$} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const toggleCollapsibleNavRef = createRef<HTMLButtonElement & { euiAnimate: () => void }>();
|
||||
const className = classnames('hide-for-sharing', 'headerGlobalNav');
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue