diff --git a/packages/core/application/core-application-browser-internal/src/application_service.tsx b/packages/core/application/core-application-browser-internal/src/application_service.tsx index b30f4ce650730..e4bb3abedea46 100644 --- a/packages/core/application/core-application-browser-internal/src/application_service.tsx +++ b/packages/core/application/core-application-browser-internal/src/application_service.tsx @@ -374,6 +374,7 @@ export class ApplicationService { setAppActionMenu={this.setAppActionMenu} setIsMounting={(isMounting) => httpLoadingCount$.next(isMounting ? 1 : 0)} hasCustomBranding$={this.hasCustomBranding$} + appId$={this.currentAppId$} /> ); }, diff --git a/packages/core/application/core-application-browser-internal/src/ui/app_router.tsx b/packages/core/application/core-application-browser-internal/src/ui/app_router.tsx index 766067bc9f189..0542d72f10eae 100644 --- a/packages/core/application/core-application-browser-internal/src/ui/app_router.tsx +++ b/packages/core/application/core-application-browser-internal/src/ui/app_router.tsx @@ -8,9 +8,9 @@ import React, { FunctionComponent, useMemo } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { Router, Routes, Route } from '@kbn/shared-ux-router'; +import { Router, Routes, Route, RouterProvider } from '@kbn/shared-ux-router'; import { History } from 'history'; -import { EMPTY, Observable } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import type { CoreTheme } from '@kbn/core-theme-browser'; @@ -18,6 +18,7 @@ import type { MountPoint } from '@kbn/core-mount-utils-browser'; import { type AppLeaveHandler, AppStatus } from '@kbn/core-application-browser'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; +import { ExecutionContextService } from '@kbn/core-execution-context-browser-internal'; import type { Mounter } from '../types'; import { AppContainer } from './app_container'; import { CoreScopedHistory } from '../scoped_history'; @@ -32,6 +33,7 @@ interface Props { setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void; setIsMounting: (isMounting: boolean) => void; hasCustomBranding$?: Observable; + appId$: BehaviorSubject; } interface Params { @@ -48,6 +50,7 @@ export const AppRouter: FunctionComponent = ({ appStatuses$, setIsMounting, hasCustomBranding$, + appId$, }) => { const appStatuses = useObservable(appStatuses$, new Map()); const createScopedHistory = useMemo( @@ -56,66 +59,69 @@ export const AppRouter: FunctionComponent = ({ ); const showPlainSpinner = useObservable(hasCustomBranding$ ?? EMPTY, false); + const { context$, get, set, clear } = new ExecutionContextService().start({ curApp$: appId$ }); return ( - - - {[...mounters].map(([appId, mounter]) => ( + + + + {[...mounters].map(([appId, mounter]) => ( + ( + + )} + /> + ))} + {/* handler for legacy apps and used as a catch-all to display 404 page on not existing /app/appId apps*/} ( - - )} + path="/app/:appId" + render={({ + match: { + params: { appId }, + url, + }, + }: RouteComponentProps) => { + // the id/mounter retrieval can be removed once #76348 is addressed + const [id, mounter] = mounters.has(appId) ? [appId, mounters.get(appId)] : []; + return ( + + ); + }} /> - ))} - {/* handler for legacy apps and used as a catch-all to display 404 page on not existing /app/appId apps*/} - ) => { - // the id/mounter retrieval can be removed once #76348 is addressed - const [id, mounter] = mounters.has(appId) ? [appId, mounters.get(appId)] : []; - return ( - - ); - }} - /> - - + + + ); diff --git a/packages/core/application/core-application-browser-internal/tsconfig.json b/packages/core/application/core-application-browser-internal/tsconfig.json index 497d069efc596..225eb4388f1ad 100644 --- a/packages/core/application/core-application-browser-internal/tsconfig.json +++ b/packages/core/application/core-application-browser-internal/tsconfig.json @@ -37,6 +37,7 @@ "@kbn/core-analytics-browser", "@kbn/shared-ux-router", "@kbn/shared-ux-error-boundary", + "@kbn/core-execution-context-browser-internal", ], "exclude": [ "target/**/*", diff --git a/packages/shared-ux/router/impl/index.ts b/packages/shared-ux/router/impl/index.ts index 8a585afce4c48..8b9cec724a3f3 100644 --- a/packages/shared-ux/router/impl/index.ts +++ b/packages/shared-ux/router/impl/index.ts @@ -9,3 +9,4 @@ export { Route } from './route'; export { HashRouter, BrowserRouter, MemoryRouter, Router } from './router'; export { Routes } from './routes'; +export { RouterProvider } from './services'; diff --git a/packages/shared-ux/router/impl/route.tsx b/packages/shared-ux/router/impl/route.tsx index 31cd4e466601b..82c0a43f36e5a 100644 --- a/packages/shared-ux/router/impl/route.tsx +++ b/packages/shared-ux/router/impl/route.tsx @@ -71,7 +71,7 @@ export const Route = ({ * The match propagator that is part of the Route */ export const MatchPropagator = () => { - const { executionContext } = useKibanaSharedUX().services; + const { executionContext } = useKibanaSharedUX().services.coreStart?.http; const match = useRouteMatch(); useSharedUXExecutionContext(executionContext, { diff --git a/packages/shared-ux/router/impl/router.test.tsx b/packages/shared-ux/router/impl/router.test.tsx new file mode 100644 index 0000000000000..722d5dd30bd02 --- /dev/null +++ b/packages/shared-ux/router/impl/router.test.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { render } from '@testing-library/react'; +import { RouterProvider } from './services'; +import { KibanaSharedUXRouterProviderDeps } from '@kbn/shared-ux-router-types'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; + +describe(' { + let mockContext$: KibanaSharedUXRouterProviderDeps['context$']; + + beforeEach(() => { + mockContext$ = executionContextServiceMock; + }); + + it('should render', async () => { + const { findByTestId } = render( + + ); + expect(findByTestId('router-shared-ux')).toBeTruthy(); + }); +}); diff --git a/packages/shared-ux/router/impl/router.tsx b/packages/shared-ux/router/impl/router.tsx index b2bbf85cb1e29..fd680d8bc5e80 100644 --- a/packages/shared-ux/router/impl/router.tsx +++ b/packages/shared-ux/router/impl/router.tsx @@ -42,7 +42,7 @@ export const MemoryRouter = ({ children, ...props }: MemoryRouterProps) => ( ); export const Router = ({ children, ...props }: RouterProps) => ( - + {children} ); diff --git a/packages/shared-ux/router/impl/services.ts b/packages/shared-ux/router/impl/services.ts deleted file mode 100644 index 78150b576905b..0000000000000 --- a/packages/shared-ux/router/impl/services.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Observable } from 'rxjs'; -import { createContext, useContext } from 'react'; -import { SharedUXExecutionContext } from './types'; - -/** - * @public Execution context start and setup types are the same - */ -export declare type SharedUXExecutionContextStart = SharedUXExecutionContextSetup; - -/** - * Reduced the interface from ExecutionContextSetup from '@kbn/core-execution-context-browser' to only include properties needed for the Route - */ -export interface SharedUXExecutionContextSetup { - /** - * The current context observable - **/ - context$: Observable; - /** - * Set the current top level context - **/ - set(c$: SharedUXExecutionContext): void; - /** - * Get the current top level context - **/ - get(): SharedUXExecutionContext; - /** - * clears the context - **/ - clear(): void; -} - -/** - * Taken from Core services exposed to the `Plugin` start lifecycle - * - * @public - * - * @internalRemarks We document the properties with - * \@link tags to improve - * navigation in the generated docs until there's a fix for - * https://github.com/Microsoft/web-build-tools/issues/1237 - */ -export interface SharedUXExecutionContextSetup { - /** {@link SharedUXExecutionContextSetup} */ - executionContext: SharedUXExecutionContextStart; -} - -export type KibanaServices = Partial; - -export interface SharedUXRouterContextValue { - readonly services: Services; -} - -const defaultContextValue = { - services: {}, -}; - -export const sharedUXContext = - createContext>(defaultContextValue); - -export const useKibanaSharedUX = (): SharedUXRouterContextValue< - KibanaServices & Extra -> => - useContext( - sharedUXContext as unknown as React.Context> - ); diff --git a/packages/shared-ux/router/impl/services.tsx b/packages/shared-ux/router/impl/services.tsx new file mode 100644 index 0000000000000..8e7c54f7a24fa --- /dev/null +++ b/packages/shared-ux/router/impl/services.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { createContext, useContext, FC, PropsWithChildren, useMemo } from 'react'; +import { + KibanaSharedUXRouterProviderDeps, + SharedUXRouterServices, +} from '@kbn/shared-ux-router-types'; + +const Context = createContext(null); + +export const SharedUXRouterDepsProvider: FC> = ({ + children, + services, +}) => {children}; + +export const RouterProvider: FC> = ({ + children, + context$, + get, + set, + clear, +}) => { + const parentContext = useContext(Context); + const value: SharedUXRouterServices = useMemo(() => { + if (parentContext) { + return parentContext; + } + + return { + services: { get, set, clear, context$ }, + }; + }, [clear, context$, get, parentContext, set]); + return {children}; +}; + +/** Utility that provides context + * @internal + */ +export function useKibanaSharedUX(): SharedUXRouterServices { + const context = useContext(Context); + if (!context) { + throw new Error( + 'Kibana Shared UX Context is missing. Ensure your component or React root is wrapped with Kibana Shared UX Context.' + ); + } + return context; +} diff --git a/packages/shared-ux/router/impl/tsconfig.json b/packages/shared-ux/router/impl/tsconfig.json index 13e290e2dea0b..1f231c1fd176a 100644 --- a/packages/shared-ux/router/impl/tsconfig.json +++ b/packages/shared-ux/router/impl/tsconfig.json @@ -13,6 +13,8 @@ "**/*", ], "kbn_references": [ + "@kbn/shared-ux-router-types", + "@kbn/core-execution-context-server-mocks", ], "exclude": [ "target/**/*", diff --git a/packages/shared-ux/router/impl/use_execution_context.ts b/packages/shared-ux/router/impl/use_execution_context.ts index e2bb6168d1268..a6d8d87e4be0b 100644 --- a/packages/shared-ux/router/impl/use_execution_context.ts +++ b/packages/shared-ux/router/impl/use_execution_context.ts @@ -7,7 +7,7 @@ */ import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect'; -import { SharedUXExecutionContextSetup } from './services'; +import { SharedUXExecutionContextStart } from './services'; import { SharedUXExecutionContext } from './types'; /** @@ -16,7 +16,9 @@ import { SharedUXExecutionContext } from './types'; * @param context */ export function useSharedUXExecutionContext( - executionContext: SharedUXExecutionContextSetup | undefined, + executionContext: + | SharedUXExecutionContextStart['coreStart']['http']['executionContext'] + | undefined, context: SharedUXExecutionContext ) { useDeepCompareEffect(() => { diff --git a/packages/shared-ux/router/types/index.d.ts b/packages/shared-ux/router/types/index.d.ts index 5c2d5b68ae2e0..b30161c705507 100644 --- a/packages/shared-ux/router/types/index.d.ts +++ b/packages/shared-ux/router/types/index.d.ts @@ -5,3 +5,18 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ + +import { SharedUXRouterService } from '@kbn/shared-ux-router/services'; +import type { KibanaExecutionContext } from '@kbn/core-execution-context-common'; + +export interface SharedUXRouterServices { + services: SharedUXRouterService; +} + +// packages/core/execution-context/core-execution-context-browser/src/types.ts +export interface KibanaSharedUXRouterProviderDeps { + context$: Observable; + set(c$: KibanaExecutionContext): void; + get(): KibanaExecutionContext; + clear(): void; +} diff --git a/packages/shared-ux/router/types/tsconfig.json b/packages/shared-ux/router/types/tsconfig.json index cf858a91253d4..df5fb05e6820f 100644 --- a/packages/shared-ux/router/types/tsconfig.json +++ b/packages/shared-ux/router/types/tsconfig.json @@ -9,5 +9,9 @@ ], "exclude": [ "target/**/*", + ], + "kbn_references": [ + "@kbn/shared-ux-router", + "@kbn/core-execution-context-common", ] }