diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx new file mode 100644 index 0000000000000..874c91e8e546b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, ReactNode, Suspense } from 'react'; +import { EuiErrorBoundary } from '@elastic/eui'; +import { Loading } from './loading'; + +export const ExtensionWrapper = memo<{ children: ReactNode }>(({ children }) => { + return ( + + }>{children} + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts new file mode 100644 index 0000000000000..93bc1eae28cf6 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { UIExtensionPoint, UIExtensionsStorage } from '../types'; + +export const UIExtensionsContext = React.createContext({}); + +type NarrowExtensionPoint = A extends { + view: V; +} + ? A + : never; + +export const useUIExtension = ( + packageName: UIExtensionPoint['package'], + view: V +): NarrowExtensionPoint['component'] | undefined => { + const registeredExtensions = useContext(UIExtensionsContext); + + if (!registeredExtensions) { + throw new Error('useUIExtension called outside of UIExtensionsContext'); + } + + const extension = registeredExtensions?.[packageName]?.[view]; + + if (extension) { + // FIXME:PT Revisit ignore below and see if TS error can be addressed + // @ts-ignore + return extension.component; + } +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index a49306f8e8d55..d4e652ad95831 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -36,6 +36,8 @@ import { import { PackageInstallProvider } from './sections/epm/hooks'; import { FleetStatusProvider, useBreadcrumbs } from './hooks'; import { IntraAppStateProvider } from './hooks/use_intra_app_state'; +import { UIExtensionsStorage } from './types'; +import { UIExtensionsContext } from './hooks/use_ui_extension'; export interface ProtectedRouteProps extends RouteProps { isAllowed?: boolean; @@ -235,6 +237,7 @@ const IngestManagerApp = ({ config, history, kibanaVersion, + extensions, }: { basepath: string; coreStart: CoreStart; @@ -243,6 +246,7 @@ const IngestManagerApp = ({ config: IngestManagerConfigType; history: AppMountParameters['history']; kibanaVersion: string; + extensions: UIExtensionsStorage; }) => { const isDarkMode = useObservable(coreStart.uiSettings.get$('theme:darkMode')); return ( @@ -252,7 +256,9 @@ const IngestManagerApp = ({ - + + + @@ -268,7 +274,8 @@ export function renderApp( setupDeps: IngestManagerSetupDeps, startDeps: IngestManagerStartDeps, config: IngestManagerConfigType, - kibanaVersion: string + kibanaVersion: string, + extensions: UIExtensionsStorage ) { ReactDOM.render( , element ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx deleted file mode 100644 index d5163e1b9abbe..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx +++ /dev/null @@ -1,61 +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; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; -import { NewPackagePolicy } from '../../../../types'; -import { CreatePackagePolicyFrom } from '../types'; - -export interface CustomConfigurePackagePolicyProps { - packageName: string; - from: CreatePackagePolicyFrom; - packagePolicy: NewPackagePolicy; - packagePolicyId?: string; -} - -/** - * Custom content type that external plugins can provide to Ingest's - * package policy UI. - */ -export type CustomConfigurePackagePolicyContent = React.FC; - -type AllowedPackageKey = 'endpoint'; -const PackagePolicyMapping: { - [key: string]: CustomConfigurePackagePolicyContent; -} = {}; - -/** - * Plugins can call this function from the start lifecycle to - * register a custom component in the Ingest package policy. - */ -export function registerPackagePolicyComponent( - key: AllowedPackageKey, - value: CustomConfigurePackagePolicyContent -) { - PackagePolicyMapping[key] = value; -} - -const EmptyPackagePolicy: CustomConfigurePackagePolicyContent = () => ( - -

- -

- - } - /> -); - -export const CustomPackagePolicy = (props: CustomConfigurePackagePolicyProps) => { - const CustomPackagePolicyContent = PackagePolicyMapping[props.packageName] || EmptyPackagePolicy; - return ; -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts index 893ed00cca9ac..58b5b1cd3126e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts @@ -6,4 +6,3 @@ export { CreatePackagePolicyPageLayout } from './layout'; export { PackagePolicyInputPanel } from './package_policy_input_panel'; export { PackagePolicyInputVarField } from './package_policy_input_var_field'; -export { CustomPackagePolicy } from './custom_package_policy'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index b45794b9f87db..a837ed33e4110 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -46,6 +46,9 @@ import { StepSelectAgentPolicy } from './step_select_agent_policy'; import { StepConfigurePackagePolicy } from './step_configure_package'; import { StepDefinePackagePolicy } from './step_define_package_policy'; import { useIntraAppState } from '../../../hooks/use_intra_app_state'; +import { useUIExtension } from '../../../hooks/use_ui_extension'; +import { ExtensionWrapper } from '../../../components/extension_wrapper'; +import { PackagePolicyEditExtensionComponentProps } from '../../../types'; const StepsWithLessPadding = styled(EuiSteps)` .euiStep__content { @@ -191,6 +194,21 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { [packagePolicy, updatePackagePolicyValidation] ); + const handleExtensionViewOnChange = useCallback< + PackagePolicyEditExtensionComponentProps['onChange'] + >( + ({ isValid, updatedPolicy }) => { + updatePackagePolicy(updatedPolicy); + setFormState((prevState) => { + if (prevState === 'VALID' && !isValid) { + return 'INVALID'; + } + return prevState; + }); + }, + [updatePackagePolicy] + ); + // Cancel path const cancelUrl = useMemo(() => { if (routeState && routeState.onCancelUrl) { @@ -287,6 +305,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { [pkgkey, updatePackageInfo, agentPolicy, updateAgentPolicy] ); + const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); + const stepSelectPackage = useMemo( () => ( { validationResults={validationResults!} submitAttempted={formState === 'INVALID'} /> + {/* If an Agent Policy and a package has been selected, then show UI extension (if any) */} + {packagePolicy.policy_id && packagePolicy.package?.name && ExtensionView && ( + + + + )} ) : (
), [ - agentPolicy, - formState, isLoadingSecondStep, - packagePolicy, + agentPolicy, packageInfo, + packagePolicy, updatePackagePolicy, validationResults, + formState, + ExtensionView, + handleExtensionViewOnChange, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index b335ff439684b..671bc829af82a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -4,7 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiEmptyPrompt, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import { PackageInfo, RegistryStream, @@ -13,8 +20,9 @@ import { } from '../../../types'; import { Loading } from '../../../components'; import { PackagePolicyValidationResults } from './services'; -import { PackagePolicyInputPanel, CustomPackagePolicy } from './components'; +import { PackagePolicyInputPanel } from './components'; import { CreatePackagePolicyFrom } from './types'; +import { useUIExtension } from '../../../hooks/use_ui_extension'; const findStreamsForInputType = ( inputType: string, @@ -55,6 +63,12 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ validationResults, submitAttempted, }) => { + const hasUiExtension = + useUIExtension( + packageInfo.name, + from === 'edit' ? 'package-policy-edit' : 'package-policy-create' + ) !== undefined; + // Configure inputs (and their streams) // Assume packages only export one config template for now const renderConfigureInputs = () => @@ -98,12 +112,20 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ })} - ) : ( - +

+ +

+ + } /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index d642619515a57..bfc10848d378f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -41,6 +41,10 @@ import { } from '../create_package_policy_page/types'; import { StepConfigurePackagePolicy } from '../create_package_policy_page/step_configure_package'; import { StepDefinePackagePolicy } from '../create_package_policy_page/step_define_package_policy'; +import { useUIExtension } from '../../../hooks/use_ui_extension'; +import { ExtensionWrapper } from '../../../components/extension_wrapper'; +import { GetOnePackagePolicyResponse } from '../../../../../../common/types/rest_spec'; +import { PackagePolicyEditExtensionComponentProps } from '../../../types'; export const EditPackagePolicyPage: React.FunctionComponent = () => { const { notifications } = useCore(); @@ -68,6 +72,9 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { inputs: [], version: '', }); + const [originalPackagePolicy, setOriginalPackagePolicy] = useState< + GetOnePackagePolicyResponse['item'] + >(); // Retrieve agent policy, package, and package policy info useEffect(() => { @@ -83,6 +90,8 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { setAgentPolicy(agentPolicyData.item); } if (packagePolicyData?.item) { + setOriginalPackagePolicy(packagePolicyData.item); + const { id, revision, @@ -189,6 +198,21 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { [packagePolicy, updatePackagePolicyValidation] ); + const handleExtensionViewOnChange = useCallback< + PackagePolicyEditExtensionComponentProps['onChange'] + >( + ({ isValid, updatedPolicy }) => { + updatePackagePolicy(updatedPolicy); + setFormState((prevState) => { + if (prevState === 'VALID' && !isValid) { + return 'INVALID'; + } + return prevState; + }); + }, + [updatePackagePolicy] + ); + // Cancel url const cancelUrl = getHref('policy_details', { policyId }); @@ -267,6 +291,8 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { packageInfo, }; + const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit'); + const configurePackage = useMemo( () => agentPolicy && packageInfo ? ( @@ -288,16 +314,32 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { validationResults={validationResults!} submitAttempted={formState === 'INVALID'} /> + + {packagePolicy.policy_id && + packagePolicy.package?.name && + originalPackagePolicy && + ExtensionView && ( + + + + )} ) : null, [ agentPolicy, - formState, - packagePolicy, - packagePolicyId, packageInfo, + packagePolicy, updatePackagePolicy, validationResults, + packagePolicyId, + formState, + originalPackagePolicy, + ExtensionView, + handleExtensionViewOnChange, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts new file mode 100644 index 0000000000000..97c0203fab056 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { lazy } from 'react'; + +import { + PackagePolicyEditExtensionComponent, + UIExtensionRegistrationCallback, + UIExtensionsStorage, +} from '../types'; +import { createExtensionRegistrationCallback } from './ui_extensions'; + +describe('UI Extension services', () => { + describe('When using createExtensionRegistrationCallback factory', () => { + let storage: UIExtensionsStorage; + let register: UIExtensionRegistrationCallback; + + beforeEach(() => { + storage = {}; + register = createExtensionRegistrationCallback(storage); + }); + + it('should return a function', () => { + expect(register).toBeInstanceOf(Function); + }); + + it('should store an extension points', () => { + const LazyCustomView = lazy(async () => { + return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + }); + register({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + + expect(storage.endpoint['package-policy-edit']).toEqual({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + }); + + it('should throw if extension point has already registered', () => { + const LazyCustomView = lazy(async () => { + return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + }); + const LazyCustomView2 = lazy(async () => { + return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + }); + + register({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + + expect(() => { + register({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView2, + }); + }).toThrow(); + + expect(storage.endpoint['package-policy-edit']).toEqual({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts new file mode 100644 index 0000000000000..5af9122d4f12a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UIExtensionRegistrationCallback, UIExtensionsStorage } from '../types'; + +/** Factory that returns a callback that can be used to register UI extensions */ +export const createExtensionRegistrationCallback = ( + storage: UIExtensionsStorage +): UIExtensionRegistrationCallback => { + return (extensionPoint) => { + const { package: packageName, view } = extensionPoint; + + if (!storage[packageName]) { + storage[packageName] = {}; + } + + if (storage[packageName]?.[view]) { + throw new Error(`Extension point has already been registered: [${packageName}][${view}]`); + } + + storage[packageName][view] = extensionPoint; + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts index 1cf8077aeda40..78cb355318d40 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts @@ -119,3 +119,5 @@ export { } from '../../../../common'; export * from './intra_app_route_state'; + +export * from './ui_extensions'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts new file mode 100644 index 0000000000000..fbede8af95b66 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ComponentType, LazyExoticComponent } from 'react'; +import { NewPackagePolicy, PackagePolicy } from './index'; + +/** Register a Fleet UI extension */ +export type UIExtensionRegistrationCallback = (extensionPoint: UIExtensionPoint) => void; + +/** Internal storage for registered UI Extension Points */ +export interface UIExtensionsStorage { + [key: string]: Partial>; +} + +/** + * UI Component Extension is used on the pages displaying the ability to edit an + * Integration Policy + */ +export type PackagePolicyEditExtensionComponent = ComponentType< + PackagePolicyEditExtensionComponentProps +>; + +export interface PackagePolicyEditExtensionComponentProps { + /** The current integration policy being edited */ + policy: PackagePolicy; + /** The new (updated) integration policy that will be saved */ + newPolicy: NewPackagePolicy; + /** + * A callback that should be executed anytime a change to the Integration Policy needs to + * be reported back to the Fleet Policy Edit page + */ + onChange: (opts: { + /** is current form state is valid */ + isValid: boolean; + /** The updated Integration Policy to be merged back and included in the API call */ + updatedPolicy: NewPackagePolicy; + }) => void; +} + +/** Extension point registration contract for Integration Policy Edit views */ +export interface PackagePolicyEditExtension { + package: string; + view: 'package-policy-edit'; + component: LazyExoticComponent; +} + +/** + * UI Component Extension is used on the pages displaying the ability to Create an + * Integration Policy + */ +export type PackagePolicyCreateExtensionComponent = ComponentType< + PackagePolicyCreateExtensionComponentProps +>; + +export interface PackagePolicyCreateExtensionComponentProps { + /** The integration policy being created */ + newPolicy: NewPackagePolicy; + /** + * A callback that should be executed anytime a change to the Integration Policy needs to + * be reported back to the Fleet Policy Edit page + */ + onChange: (opts: { + /** is current form state is valid */ + isValid: boolean; + /** The updated Integration Policy to be merged back and included in the API call */ + updatedPolicy: NewPackagePolicy; + }) => void; +} + +/** Extension point registration contract for Integration Policy Create views */ +export interface PackagePolicyCreateExtension { + package: string; + view: 'package-policy-create'; + component: LazyExoticComponent; +} + +/** + * UI Component Extension is used to display a Custom tab (and view) under a given Integration + */ +export type PackageCustomExtensionComponent = ComponentType; + +/** Extension point registration contract for Integration details Custom view */ +export interface PackageCustomExtension { + package: string; + view: 'package-detail-custom'; + component: LazyExoticComponent; +} + +/** Fleet UI Extension Point */ +export type UIExtensionPoint = + | PackagePolicyEditExtension + | PackageCustomExtension + | PackagePolicyCreateExtension; diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index f974a8c3d3cc8..1de001a6fc69e 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -12,13 +12,8 @@ export const plugin = (initializerContext: PluginInitializerContext) => { return new IngestManagerPlugin(initializerContext); }; -export { - CustomConfigurePackagePolicyContent, - CustomConfigurePackagePolicyProps, - registerPackagePolicyComponent, -} from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; - export type { NewPackagePolicy } from './applications/fleet/types'; export * from './applications/fleet/types/intra_app_route_state'; +export * from './applications/fleet/types/ui_extensions'; export { pagePathGetters } from './applications/fleet/constants'; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 2e7cbb9cb86ab..377ba770b5ca2 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -30,7 +30,8 @@ import { TutorialDirectoryHeaderLink, TutorialModuleNotice, } from './applications/fleet/components/home_integration'; -import { registerPackagePolicyComponent } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; +import { createExtensionRegistrationCallback } from './applications/fleet/services/ui_extensions'; +import { UIExtensionRegistrationCallback, UIExtensionsStorage } from './applications/fleet/types'; export { IngestManagerConfigType } from '../common/types'; @@ -43,7 +44,7 @@ export interface IngestManagerSetup {} * Describes public IngestManager plugin contract returned at the `start` stage. */ export interface IngestManagerStart { - registerPackagePolicyComponent: typeof registerPackagePolicyComponent; + registerExtension: UIExtensionRegistrationCallback; isInitialized: () => Promise; } @@ -62,6 +63,7 @@ export class IngestManagerPlugin Plugin { private config: IngestManagerConfigType; private kibanaVersion: string; + private extensions: UIExtensionsStorage = {}; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); @@ -71,6 +73,7 @@ export class IngestManagerPlugin public setup(core: CoreSetup, deps: IngestManagerSetupDeps) { const config = this.config; const kibanaVersion = this.kibanaVersion; + const extensions = this.extensions; // Set up http client setHttpClient(core.http); @@ -92,7 +95,15 @@ export class IngestManagerPlugin IngestManagerStart ]; const { renderApp, teardownIngestManager } = await import('./applications/fleet/'); - const unmount = renderApp(coreStart, params, deps, startDeps, config, kibanaVersion); + const unmount = renderApp( + coreStart, + params, + deps, + startDeps, + config, + kibanaVersion, + extensions + ); return () => { unmount(); @@ -153,7 +164,8 @@ export class IngestManagerPlugin return successPromise; }, - registerPackagePolicyComponent, + + registerExtension: createExtensionRegistrationCallback(this.extensions), }; } diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts index ff3fe7517e64a..3388fb5355845 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IngestManagerStart, registerPackagePolicyComponent } from '../../../../../fleet/public'; +import { IngestManagerStart } from '../../../../../fleet/public'; import { dataPluginMock, Start as DataPublicStartMock, @@ -58,7 +58,7 @@ export const depsStartMock: () => DepsStartMock = () => { data: dataMock, ingestManager: { isInitialized: () => Promise.resolve(true), - registerPackagePolicyComponent, + registerExtension: jest.fn(), }, }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx new file mode 100644 index 0000000000000..69406a41fe055 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; +import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { PackagePolicyCreateExtensionComponentProps } from '../../../../../../../fleet/public'; + +/** + * Exports Endpoint-specific package policy instructions + * for use in the Ingest app create / edit package policy + */ +export const EndpointPolicyCreateExtension = memo( + () => { + return ( + <> + + + +

+ +

+
+
+ + ); + } +); +EndpointPolicyCreateExtension.displayName = 'EndpointPolicyCreateExtension'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx similarity index 81% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx rename to x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 0be5f119e5eff..b667ea965af68 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -20,9 +20,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { - CustomConfigurePackagePolicyContent, - CustomConfigurePackagePolicyProps, pagePathGetters, + PackagePolicyEditExtensionComponentProps, } from '../../../../../../../fleet/public'; import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing'; import { MANAGEMENT_APP_ID } from '../../../../common/constants'; @@ -37,42 +36,21 @@ import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoi * Exports Endpoint-specific package policy instructions * for use in the Ingest app create / edit package policy */ -export const ConfigureEndpointPackagePolicy = memo( - ({ - from, - packagePolicyId, - packagePolicy: { policy_id: agentPolicyId }, - }: CustomConfigurePackagePolicyProps) => { +export const EndpointPolicyEditExtension = memo( + ({ policy }) => { return ( <> - + - {from === 'edit' ? ( - <> - - - ) : ( -

- -

- )} +
); } ); -ConfigureEndpointPackagePolicy.displayName = 'ConfigureEndpointPackagePolicy'; +EndpointPolicyEditExtension.displayName = 'EndpointPolicyEditExtension'; const EditFlowMessage = memo<{ agentPolicyId: string; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx new file mode 100644 index 0000000000000..b7a6fa36e4eb7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { lazy } from 'react'; +import { PackagePolicyCreateExtensionComponent } from '../../../../../../../fleet/public'; + +export const LazyEndpointPolicyCreateExtension = lazy( + async () => { + const { EndpointPolicyCreateExtension } = await import('./endpoint_policy_create_extension'); + return { + default: EndpointPolicyCreateExtension, + }; + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx new file mode 100644 index 0000000000000..b417bc9ad5d9c --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { lazy } from 'react'; +import { PackagePolicyEditExtensionComponent } from '../../../../../../../fleet/public'; + +export const LazyEndpointPolicyEditExtension = lazy( + async () => { + const { EndpointPolicyEditExtension } = await import('./endpoint_policy_edit_extension'); + return { + // FIXME: remove casting once old UI component registration is removed + default: (EndpointPolicyEditExtension as unknown) as PackagePolicyEditExtensionComponent, + }; + } +); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 08c780d4a7203..5895880adb26a 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; import { pluck } from 'rxjs/operators'; - import { PluginSetup, PluginStart, @@ -44,8 +43,6 @@ import { DEFAULT_INDEX_KEY, } from '../common/constants'; -import { ConfigureEndpointPackagePolicy } from './management/pages/policy/view/ingest_manager_integration/configure_package_policy'; - import { SecurityPageName } from './app/types'; import { manageOldSiemRoutes } from './helpers'; import { @@ -63,6 +60,8 @@ import { } from '../common/search_strategy/index_fields'; import { SecurityAppStore } from './common/store/store'; import { getCaseConnectorUI } from './common/lib/connectors'; +import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; +import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; export class Plugin implements IPlugin { private kibanaVersion: string; @@ -332,10 +331,19 @@ export class Plugin implements IPlugin