Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.17] Fix context.pageName by fixing missing executionContext and add enableExecutionContextTracking flag (#204547) #204807

Open
wants to merge 3 commits into
base: 8.17
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions packages/react/kibana_context/root/root_provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ import { useEuiTheme } from '@elastic/eui';
import type { UseEuiTheme } from '@elastic/eui';
import { mountWithIntl } from '@kbn/test-jest-helpers';
import type { KibanaTheme } from '@kbn/react-kibana-context-common';
import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import type { ExecutionContextStart } from '@kbn/core-execution-context-browser';
import { executionContextServiceMock } from '@kbn/core-execution-context-browser-mocks';
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
import { KibanaRootContextProvider } from './root_provider';
import { I18nStart } from '@kbn/core-i18n-browser';
import { KibanaRootContextProvider } from './root_provider';

describe('KibanaRootContextProvider', () => {
let euiTheme: UseEuiTheme | undefined;
let i18nMock: I18nStart;
let analytics: AnalyticsServiceStart;
let executionContext: ExecutionContextStart;

beforeEach(() => {
euiTheme = undefined;
analytics = analyticsServiceMock.createAnalyticsServiceStart();
i18nMock = i18nServiceMock.createStartContract();
executionContext = executionContextServiceMock.createStartContract();
});

const flushPromises = async () => {
Expand Down Expand Up @@ -62,8 +62,8 @@ describe('KibanaRootContextProvider', () => {

const wrapper = mountWithIntl(
<KibanaRootContextProvider
analytics={analytics}
i18n={i18nMock}
executionContext={executionContext}
theme={{ theme$: of(coreTheme) }}
>
<InnerComponent />
Expand All @@ -80,8 +80,8 @@ describe('KibanaRootContextProvider', () => {

const wrapper = mountWithIntl(
<KibanaRootContextProvider
analytics={analytics}
i18n={i18nMock}
executionContext={executionContext}
theme={{ theme$: coreTheme$ }}
>
<InnerComponent />
Expand Down
18 changes: 12 additions & 6 deletions packages/react/kibana_context/root/root_provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import React, { FC, PropsWithChildren } from 'react';

import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { I18nStart } from '@kbn/core-i18n-browser';
import type { ExecutionContextStart } from '@kbn/core-execution-context-browser';
import { SharedUXRouterContext } from '@kbn/shared-ux-router';

// @ts-expect-error EUI exports this component internally, but Kibana isn't picking it up its types
import { useIsNestedEuiProvider } from '@elastic/eui/lib/components/provider/nested';
Expand All @@ -25,6 +27,8 @@ export interface KibanaRootContextProviderProps extends KibanaEuiProviderProps {
i18n: I18nStart;
/** The `AnalyticsServiceStart` API from `CoreStart`. */
analytics?: Pick<AnalyticsServiceStart, 'reportEvent'>;
/** The `ExecutionContextStart` API from `CoreStart`. */
executionContext?: ExecutionContextStart;
}

/**
Expand All @@ -44,20 +48,22 @@ export interface KibanaRootContextProviderProps extends KibanaEuiProviderProps {
export const KibanaRootContextProvider: FC<PropsWithChildren<KibanaRootContextProviderProps>> = ({
children,
i18n,
executionContext,
...props
}) => {
const hasEuiProvider = useIsNestedEuiProvider();
const rootContextProvider = (
<SharedUXRouterContext.Provider value={{ services: { executionContext } }}>
<i18n.Context>{children}</i18n.Context>
</SharedUXRouterContext.Provider>
);

if (hasEuiProvider) {
emitEuiProviderWarning(
'KibanaRootContextProvider has likely been nested in this React tree, either by direct reference or by KibanaRenderContextProvider. The result of this nesting is a nesting of EuiProvider, which has negative effects. Check your React tree for nested Kibana context providers.'
);
return <i18n.Context>{children}</i18n.Context>;
return rootContextProvider;
} else {
return (
<KibanaEuiProvider {...props}>
<i18n.Context>{children}</i18n.Context>
</KibanaEuiProvider>
);
return <KibanaEuiProvider {...props}>{rootContextProvider}</KibanaEuiProvider>;
}
};
4 changes: 3 additions & 1 deletion packages/react/kibana_context/root/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"@kbn/core-i18n-browser",
"@kbn/core-base-common",
"@kbn/core-analytics-browser",
"@kbn/core-analytics-browser-mocks",
"@kbn/core-execution-context-browser",
"@kbn/core-execution-context-browser-mocks",
"@kbn/shared-ux-router"
]
}
34 changes: 34 additions & 0 deletions packages/shared-ux/router/impl/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")

SRCS = glob(
[
"**/*.ts",
"**/*.tsx",
],
exclude = [
"**/test_helpers.ts",
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)

DEPS = [

]

js_library(
name = "shared-ux-router",
package_name = "@kbn/shared-ux-router",
srcs = ["package.json"] + SRCS,
deps = DEPS,
visibility = ["//visibility:public"],
)

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/shared-ux/router/impl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
export { Route } from './route';
export { HashRouter, BrowserRouter, MemoryRouter, Router } from './router';
export { Routes } from './routes';

export { SharedUXRouterContext } from './services';
19 changes: 19 additions & 0 deletions packages/shared-ux/router/impl/route.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,34 @@

import React, { Component, FC } from 'react';
import { shallow } from 'enzyme';
import { useSharedUXRoutesContext } from './routes_context';
import { Route } from './route';
import { createMemoryHistory } from 'history';

jest.mock('./routes_context', () => ({
useSharedUXRoutesContext: jest.fn().mockImplementation(() => ({
enableExecutionContextTracking: true,
})),
}));

describe('Route', () => {
beforeEach(() => {
jest.restoreAllMocks();
});

test('renders', () => {
const example = shallow(<Route />);
expect(example).toMatchSnapshot();
});

test('renders with enableExecutionContextTracking as false', () => {
(useSharedUXRoutesContext as jest.Mock).mockImplementationOnce(() => ({
enableExecutionContextTracking: false,
}));
const example = shallow(<Route />);
expect(example).toMatchSnapshot();
});

test('location renders as expected', () => {
// create a history
const historyLocation = createMemoryHistory();
Expand Down
16 changes: 12 additions & 4 deletions packages/shared-ux/router/impl/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
RouteProps,
useRouteMatch,
} from 'react-router-dom';
import { useSharedUXRoutesContext } from './routes_context';
import { useKibanaSharedUX } from './services';
import { useSharedUXExecutionContext } from './use_execution_context';

Expand All @@ -30,17 +31,18 @@ export const Route = <T extends {}>({
render,
...rest
}: RouteProps<string, { [K: string]: string } & T>) => {
const { enableExecutionContextTracking } = useSharedUXRoutesContext();
const component = useMemo(() => {
if (!Component) {
return undefined;
}
return (props: RouteComponentProps) => (
<>
<MatchPropagator />
{enableExecutionContextTracking && <MatchPropagator />}
<Component {...props} />
</>
);
}, [Component]);
}, [Component, enableExecutionContextTracking]);

if (component) {
return <ReactRouterRoute {...rest} component={component} />;
Expand All @@ -52,7 +54,7 @@ export const Route = <T extends {}>({
{...rest}
render={(props) => (
<>
<MatchPropagator />
{enableExecutionContextTracking && <MatchPropagator />}
{/* @ts-ignore else condition exists if renderFunction is undefined*/}
{renderFunction(props)}
</>
Expand All @@ -62,7 +64,7 @@ export const Route = <T extends {}>({
}
return (
<ReactRouterRoute {...rest}>
<MatchPropagator />
{enableExecutionContextTracking && <MatchPropagator />}
{children}
</ReactRouterRoute>
);
Expand All @@ -75,6 +77,12 @@ export const MatchPropagator = () => {
const { executionContext } = useKibanaSharedUX().services;
const match = useRouteMatch();

if (!executionContext && process.env.NODE_ENV !== 'production') {
throw new Error(
'Default execution context tracking is enabled but the executionContext service is not available'
);
}

useSharedUXExecutionContext(executionContext, {
type: 'application',
page: match.path,
Expand Down
62 changes: 34 additions & 28 deletions packages/shared-ux/router/impl/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React, { Children } from 'react';
import { Switch, useRouteMatch } from 'react-router-dom';
import { Routes as ReactRouterRoutes, Route } from 'react-router-dom-v5-compat';
import { Route as LegacyRoute, MatchPropagator } from './route';
import { SharedUXRoutesContext } from './routes_context';

type RouterElementChildren = Array<
React.ReactElement<
Expand All @@ -28,42 +29,47 @@ type RouterElementChildren = Array<

export const Routes = ({
legacySwitch = true,
enableExecutionContextTracking = false,
children,
}: {
legacySwitch?: boolean;
enableExecutionContextTracking?: boolean;
children: React.ReactNode;
}) => {
const match = useRouteMatch();

return legacySwitch ? (
<Switch>{children}</Switch>
<SharedUXRoutesContext.Provider value={{ enableExecutionContextTracking }}>
<Switch>{children}</Switch>
</SharedUXRoutesContext.Provider>
) : (
<ReactRouterRoutes>
{Children.map(children as RouterElementChildren, (child) => {
if (React.isValidElement(child) && child.type === LegacyRoute) {
const path = replace(child?.props.path, match.url + '/', '');
const renderFunction =
typeof child?.props.children === 'function'
? child?.props.children
: child?.props.render;

return (
<Route
path={path}
element={
<>
<MatchPropagator />
{(child?.props?.component && <child.props.component />) ||
(renderFunction && renderFunction()) ||
children}
</>
}
/>
);
} else {
return child;
}
})}
</ReactRouterRoutes>
<SharedUXRoutesContext.Provider value={{ enableExecutionContextTracking }}>
<ReactRouterRoutes>
{Children.map(children as RouterElementChildren, (child) => {
if (React.isValidElement(child) && child.type === LegacyRoute) {
const path = replace(child?.props.path, match.url + '/', '');
const renderFunction =
typeof child?.props.children === 'function'
? child?.props.children
: child?.props.render;
return (
<Route
path={path}
element={
<>
{enableExecutionContextTracking && <MatchPropagator />}
{(child?.props?.component && <child.props.component />) ||
(renderFunction && renderFunction()) ||
children}
</>
}
/>
);
} else {
return child;
}
})}
</ReactRouterRoutes>
</SharedUXRoutesContext.Provider>
);
};
18 changes: 18 additions & 0 deletions packages/shared-ux/router/impl/routes_context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { createContext, useContext } from 'react';
import { SharedUXRoutesContextType } from './types';

const defaultContextValue = {};

export const SharedUXRoutesContext = createContext<SharedUXRoutesContextType>(defaultContextValue);

export const useSharedUXRoutesContext = (): SharedUXRoutesContextType =>
useContext(SharedUXRoutesContext as unknown as React.Context<SharedUXRoutesContextType>);
8 changes: 5 additions & 3 deletions packages/shared-ux/router/impl/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface SharedUXExecutionContextSetup {
*/
export interface SharedUXExecutionContextSetup {
/** {@link SharedUXExecutionContextSetup} */
executionContext: SharedUXExecutionContextStart;
executionContext?: SharedUXExecutionContextStart;
}

export type KibanaServices = Partial<SharedUXExecutionContextSetup>;
Expand All @@ -63,12 +63,14 @@ const defaultContextValue = {
services: {},
};

export const sharedUXContext =
export const SharedUXRouterContext =
createContext<SharedUXRouterContextValue<KibanaServices>>(defaultContextValue);

export const useKibanaSharedUX = <Extra extends object = {}>(): SharedUXRouterContextValue<
KibanaServices & Extra
> =>
useContext(
sharedUXContext as unknown as React.Context<SharedUXRouterContextValue<KibanaServices & Extra>>
SharedUXRouterContext as unknown as React.Context<
SharedUXRouterContextValue<KibanaServices & Extra>
>
);
8 changes: 8 additions & 0 deletions packages/shared-ux/router/impl/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ export declare interface SharedUXExecutionContext {
/** an inner context spawned from the current context. */
child?: SharedUXExecutionContext;
}

export declare interface SharedUXRoutesContextType {
/**
* This flag is used to enable the default execution context tracking for a specific router.
* Enable this flag in case you don't have a custom implementation for execution context tracking.
* */
readonly enableExecutionContextTracking?: boolean;
}
2 changes: 1 addition & 1 deletion packages/shared-ux/router/impl/use_execution_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
import { SharedUXExecutionContextSetup } from './services';
import type { SharedUXExecutionContextSetup } from './services';
import { SharedUXExecutionContext } from './types';

/**
Expand Down
Loading