Skip to content

Commit

Permalink
[Fleet] IngestManager Plugin interface for registering UI extensions (e…
Browse files Browse the repository at this point in the history
…lastic#82783) (elastic#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)
  • Loading branch information
paul-tavares authored Nov 12, 2020
1 parent 64d0fd8 commit 8f7b38d
Show file tree
Hide file tree
Showing 20 changed files with 478 additions and 125 deletions.
Original file line number Diff line number Diff line change
@@ -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 (
<EuiErrorBoundary>
<Suspense fallback={<Loading />}>{children}</Suspense>
</EuiErrorBoundary>
);
});
Original file line number Diff line number Diff line change
@@ -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<UIExtensionsStorage>({});

type NarrowExtensionPoint<V extends UIExtensionPoint['view'], A = UIExtensionPoint> = A extends {
view: V;
}
? A
: never;

export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionPoint['view']>(
packageName: UIExtensionPoint['package'],
view: V
): NarrowExtensionPoint<V>['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;
}
};
12 changes: 10 additions & 2 deletions x-pack/plugins/fleet/public/applications/fleet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -235,6 +237,7 @@ const IngestManagerApp = ({
config,
history,
kibanaVersion,
extensions,
}: {
basepath: string;
coreStart: CoreStart;
Expand All @@ -243,6 +246,7 @@ const IngestManagerApp = ({
config: IngestManagerConfigType;
history: AppMountParameters['history'];
kibanaVersion: string;
extensions: UIExtensionsStorage;
}) => {
const isDarkMode = useObservable<boolean>(coreStart.uiSettings.get$('theme:darkMode'));
return (
Expand All @@ -252,7 +256,9 @@ const IngestManagerApp = ({
<ConfigContext.Provider value={config}>
<KibanaVersionContext.Provider value={kibanaVersion}>
<EuiThemeProvider darkMode={isDarkMode}>
<IngestManagerRoutes history={history} basepath={basepath} />
<UIExtensionsContext.Provider value={extensions}>
<IngestManagerRoutes history={history} basepath={basepath} />
</UIExtensionsContext.Provider>
</EuiThemeProvider>
</KibanaVersionContext.Provider>
</ConfigContext.Provider>
Expand All @@ -268,7 +274,8 @@ export function renderApp(
setupDeps: IngestManagerSetupDeps,
startDeps: IngestManagerStartDeps,
config: IngestManagerConfigType,
kibanaVersion: string
kibanaVersion: string,
extensions: UIExtensionsStorage
) {
ReactDOM.render(
<IngestManagerApp
Expand All @@ -279,6 +286,7 @@ export function renderApp(
config={config}
history={history}
kibanaVersion={kibanaVersion}
extensions={extensions}
/>,
element
);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
() => (
<StepSelectPackage
Expand Down Expand Up @@ -320,18 +340,26 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
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 && (
<ExtensionWrapper>
<ExtensionView newPolicy={packagePolicy} onChange={handleExtensionViewOnChange} />
</ExtensionWrapper>
)}
</>
) : (
<div />
),
[
agentPolicy,
formState,
isLoadingSecondStep,
packagePolicy,
agentPolicy,
packageInfo,
packagePolicy,
updatePackagePolicy,
validationResults,
formState,
ExtensionView,
handleExtensionViewOnChange,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 = () =>
Expand Down Expand Up @@ -98,12 +112,20 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{
})}
</EuiFlexGroup>
</>
) : (
<CustomPackagePolicy
from={from}
packageName={packageInfo.name}
packagePolicy={packagePolicy}
packagePolicyId={packagePolicyId}
) : hasUiExtension ? null : (
<EuiEmptyPrompt
iconType="checkInCircleFilled"
iconColor="secondary"
body={
<EuiText>
<p>
<FormattedMessage
id="xpack.fleet.createPackagePolicy.stepConfigure.noPolicyOptionsMessage"
defaultMessage="Nothing to configure"
/>
</p>
</EuiText>
}
/>
);

Expand Down
Loading

0 comments on commit 8f7b38d

Please sign in to comment.