From 8f7b38dd8e4251cca278802a4951c21acd51bef4 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Thu, 12 Nov 2020 13:48:49 -0500
Subject: [PATCH] [Fleet] IngestManager Plugin interface for registering UI
extensions (#82783) (#83307)
* Expose `registerExtension()` interface on `Plugin#start`
* Refactor use of `CustomConfigurePackagePolicy` to the new registerExtension approach
* Refactor to always show registered ui extension (even if Integration has configuration options)
---
.../fleet/components/extension_wrapper.tsx | 17 ++++
.../fleet/hooks/use_ui_extension.ts | 35 +++++++
.../fleet/public/applications/fleet/index.tsx | 12 ++-
.../components/custom_package_policy.tsx | 61 ------------
.../components/index.ts | 1 -
.../create_package_policy_page/index.tsx | 34 ++++++-
.../step_configure_package.tsx | 38 ++++++--
.../edit_package_policy_page/index.tsx | 48 +++++++++-
.../fleet/services/ui_extensions.test.ts | 76 +++++++++++++++
.../fleet/services/ui_extensions.ts | 26 +++++
.../public/applications/fleet/types/index.ts | 2 +
.../applications/fleet/types/ui_extensions.ts | 96 +++++++++++++++++++
x-pack/plugins/fleet/public/index.ts | 7 +-
x-pack/plugins/fleet/public/plugin.ts | 20 +++-
.../mock/endpoint/dependencies_start_mock.ts | 4 +-
.../endpoint_policy_create_extension.tsx | 35 +++++++
...tsx => endpoint_policy_edit_extension.tsx} | 34 ++-----
.../lazy_endpoint_policy_create_extension.tsx | 17 ++++
.../lazy_endpoint_policy_edit_extension.tsx | 18 ++++
.../security_solution/public/plugin.tsx | 22 +++--
20 files changed, 478 insertions(+), 125 deletions(-)
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts
delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx
rename x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/{configure_package_policy.tsx => endpoint_policy_edit_extension.tsx} (81%)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx
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