chore: onboarding integration flow initial setup

This commit is contained in:
Angela Chuang 2024-11-12 16:31:47 +00:00 committed by Karen Grigoryan
parent 6a84cccbde
commit 637369f012
No known key found for this signature in database
29 changed files with 726 additions and 223 deletions

View file

@ -28,11 +28,15 @@ interface UseCancelParams {
}
export const useCancelAddPackagePolicy = (params: UseCancelParams) => {
const { from, pkgkey, agentPolicyId } = params;
const { from, pkgkey: pkgkeyParam, agentPolicyId } = params;
const {
application: { navigateToApp },
} = useStartServices();
const routeState = useIntraAppState<CreatePackagePolicyRouteState>();
const pkgkey = useMemo(
() => pkgkeyParam || routeState?.pkgkey,
[pkgkeyParam, routeState?.pkgkey]
);
const { getHref } = useLink();
const cancelClickHandler = useCallback(

View file

@ -15,11 +15,28 @@ import type { AddToPolicyParams, EditPackagePolicyFrom } from './types';
import { CreatePackagePolicySinglePage } from './single_page_layout';
import { CreatePackagePolicyMultiPage } from './multi_page_layout';
export const CreatePackagePolicyPage: React.FC<{}> = () => {
export const CreatePackagePolicyPage: React.FC<{
useMultiPageLayoutProp?: boolean;
originFrom?: EditPackagePolicyFrom;
propPolicyId?: string;
integrationName?: string;
setIntegrationStep?: (step: number) => void;
onCanceled?: () => void;
}> = ({
useMultiPageLayoutProp,
originFrom,
propPolicyId,
integrationName,
setIntegrationStep,
onCanceled,
}) => {
const { search } = useLocation();
const { params } = useRouteMatch<AddToPolicyParams>();
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
const useMultiPageLayout = useMemo(() => queryParams.has('useMultiPageLayout'), [queryParams]);
const useMultiPageLayout = useMemo(
() => useMultiPageLayoutProp ?? queryParams.has('useMultiPageLayout'),
[queryParams, useMultiPageLayoutProp]
);
const queryParamsPolicyId = useMemo(
() => queryParams.get('policyId') ?? undefined,
[queryParams]
@ -47,12 +64,16 @@ export const CreatePackagePolicyPage: React.FC<{}> = () => {
* creation possible if a user has not chosen one from the packages UI.
*/
const from: EditPackagePolicyFrom =
'policyId' in params || queryParamsPolicyId ? 'policy' : 'package';
originFrom ?? ('policyId' in params || queryParamsPolicyId ? 'policy' : 'package');
const pageParams = {
from,
queryParamsPolicyId,
propPolicyId,
integrationName,
prerelease,
setIntegrationStep,
onCanceled,
};
if (useMultiPageLayout) {

View file

@ -64,7 +64,6 @@ export const MultiPageStepsLayout: React.FunctionComponent<MultiPageStepLayoutPr
restrictWidth={maxWidth}
>
<StepComponent {...props} />
{packageInfo && (
<IntegrationBreadcrumb
pkgTitle={integrationInfo?.title || packageInfo.title}

View file

@ -103,6 +103,9 @@ export const InstallElasticAgentManagedPageStep: React.FC<InstallAgentPageProps>
agentCount: enrolledAgentIds.length,
showLoading: true,
poll: commandCopied,
onClickViewAgents: () => {
onNext(); // Fixme: Wording does not match what it does.
},
})
);

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { useEffect, useState, useMemo } from 'react';
import { useEffect, useState, useMemo, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { v4 as uuidv4 } from 'uuid';
import { generateNewAgentPolicyWithDefaults } from '../../../../../../../../common/services/generate_new_agent_policy';

View file

@ -18,6 +18,12 @@ import {
import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types';
import { useIntegrationsStateContext } from '../../../../../integrations/hooks';
import { CreatePackagePolicySinglePage } from '../single_page_layout';
import type { AgentPolicy } from '../../../../types';
import { useGetAgentPolicyOrDefault } from './hooks';
import {
@ -42,6 +48,13 @@ const addIntegrationStep = {
component: AddIntegrationPageStep,
};
const addIntegrationSingleLayoutStep = {
title: i18n.translate('xpack.fleet.createFirstPackagePolicy.addIntegrationStepTitle', {
defaultMessage: 'Add the integration',
}),
component: CreatePackagePolicySinglePage,
};
const confirmDataStep = {
title: i18n.translate('xpack.fleet.createFirstPackagePolicy.confirmDataStepTitle', {
defaultMessage: 'Confirm incoming data',
@ -53,23 +66,35 @@ const fleetManagedSteps = [installAgentStep, addIntegrationStep, confirmDataStep
const standaloneSteps = [addIntegrationStep, installAgentStep, confirmDataStep];
const onboardingSteps = [addIntegrationSingleLayoutStep, installAgentStep, confirmDataStep];
export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
queryParamsPolicyId,
prerelease,
from,
integrationName,
setIntegrationStep,
onCanceled,
}) => {
const { params } = useRouteMatch<AddToPolicyParams>();
const { pkgkey, policyId, integration } = params;
// fixme
const { pkgkey: pkgkeyParam, policyId, integration: integrationParam } = params;
const { pkgkey: pkgKeyContext } = useIntegrationsStateContext();
const pkgkey = pkgkeyParam || pkgKeyContext;
const { pkgName, pkgVersion } = splitPkgKey(pkgkey);
const [onSplash, setOnSplash] = useState(true);
const [onSplash, setOnSplash] = useState(from !== 'onboarding-integration');
const [currentStep, setCurrentStep] = useState(0);
const [isManaged, setIsManaged] = useState(true);
const { getHref } = useLink();
const [enrolledAgentIds, setEnrolledAgentIds] = useState<string[]>([]);
const [selectedAgentPolicies, setSelectedAgentPolicies] = useState<AgentPolicy[]>();
const toggleIsManaged = (newIsManaged: boolean) => {
setIsManaged(newIsManaged);
setCurrentStep(0);
};
const agentPolicyId = policyId || queryParamsPolicyId;
const integration = integrationName || integrationParam;
const agentPolicyId = selectedAgentPolicies?.[0]?.id || policyId || queryParamsPolicyId;
const {
data: packageInfoData,
error: packageInfoError,
@ -119,13 +144,23 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
);
}
const steps = isManaged ? fleetManagedSteps : standaloneSteps;
const stepsNext = () => {
const steps =
from === 'onboarding-integration'
? onboardingSteps
: isManaged
? fleetManagedSteps
: standaloneSteps;
const stepsNext = (props?: { selectedAgentPolicies: AgentPolicy[] }) => {
if (currentStep === steps.length - 1) {
return;
}
setCurrentStep(currentStep + 1);
setIntegrationStep(currentStep + 1);
if (props?.selectedAgentPolicies) {
setSelectedAgentPolicies(props?.selectedAgentPolicies);
}
};
const stepsBack = () => {
@ -154,6 +189,7 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
setIsManaged={toggleIsManaged}
setEnrolledAgentIds={setEnrolledAgentIds}
enrolledAgentIds={enrolledAgentIds}
onCanceled={onCanceled}
/>
);
};

View file

@ -76,6 +76,8 @@ import { generateNewAgentPolicyWithDefaults } from '../../../../../../../common/
import { packageHasAtLeastOneSecret } from '../utils';
import { useIntegrationsStateContext } from '../../../../../integrations/hooks';
import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from './components';
import { useDevToolsRequest, useOnSubmit, useSetupTechnology } from './hooks';
import { PostInstallCloudFormationModal } from './components/cloud_security_posture/post_install_cloud_formation_modal';
@ -105,12 +107,17 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
from,
queryParamsPolicyId,
prerelease,
onNext,
onCanceled,
}) => {
const {
agents: { enabled: isFleetEnabled },
} = useConfig();
const hasFleetAddAgentsPrivileges = useAuthz().fleet.addAgents;
const { params } = useRouteMatch<AddToPolicyParams>();
const { pkgkey: pkgKeyContext } = useIntegrationsStateContext();
const pkgkey = params.pkgkey || pkgKeyContext;
const fleetStatus = useFleetStatus();
const { docLinks } = useStartServices();
const spaceSettings = useSpaceSettingsContext();
@ -130,7 +137,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
queryParamsPolicyId ? SelectedPolicyTab.EXISTING : SelectedPolicyTab.NEW
);
const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey);
const { pkgName, pkgVersion } = splitPkgKey(pkgkey);
// Fetch package info
const {
data: packageInfoData,
@ -187,6 +194,24 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
hasFleetAddAgentsPrivileges,
});
const handleNavigateAddAgent = useCallback(() => {
if (onNext) {
onNext({ selectedAgentPolicies: agentPolicies });
} else {
if (savedPackagePolicy) {
navigateAddAgent(savedPackagePolicy);
}
}
}, [onNext, agentPolicies, savedPackagePolicy, navigateAddAgent]);
const handleCancellation = useCallback(() => {
if (onCanceled) {
onCanceled();
} else {
navigateAddAgentHelp(savedPackagePolicy);
}
}, [onCanceled, savedPackagePolicy, navigateAddAgentHelp]);
const setPolicyValidation = useCallback(
(selectedTab: SelectedPolicyTab, updatedAgentPolicy: NewAgentPolicy) => {
if (selectedTab === SelectedPolicyTab.NEW) {
@ -230,7 +255,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
from,
pkgkey: params.pkgkey,
pkgkey,
agentPolicyId: agentPolicyIds[0],
});
useEffect(() => {
@ -504,7 +529,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
agentCount={agentCount}
agentPolicies={agentPolicies}
onConfirm={onSubmit}
onCancel={() => setFormState('VALID')}
onCancel={() => {
setFormState('VALID');
onCanceled?.();
}}
/>
)}
{formState === 'SUBMITTED_NO_AGENTS' &&
@ -513,8 +541,8 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
savedPackagePolicy && (
<PostInstallAddAgentModal
packageInfo={packageInfo}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
onConfirm={handleNavigateAddAgent}
onCancel={handleCancellation}
/>
)}
{formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' &&
@ -523,8 +551,8 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
<PostInstallAzureArmTemplateModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
onConfirm={handleNavigateAddAgent}
onCancel={handleCancellation}
/>
)}
{formState === 'SUBMITTED_CLOUD_FORMATION' &&
@ -533,8 +561,8 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
<PostInstallCloudFormationModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
onConfirm={handleNavigateAddAgent}
onCancel={handleCancellation}
/>
)}
{formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' &&
@ -543,8 +571,8 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
<PostInstallGoogleCloudShellModal
agentPolicy={agentPolicies[0]}
packagePolicy={savedPackagePolicy}
onConfirm={() => navigateAddAgent(savedPackagePolicy)}
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
onConfirm={handleNavigateAddAgent}
onCancel={handleCancellation}
/>
)}
{packageInfo && (
@ -603,7 +631,12 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
<StepsWithLessPadding steps={steps} />
<EuiSpacer size="xl" />
<EuiSpacer size="xl" />
<CustomEuiBottomBar data-test-subj="integrationsBottomBar">
{/* Only show render button bar in portal when enableRouts is false*/}
<CustomEuiBottomBar
data-test-subj="integrationsBottomBar"
usePortal={false}
position="sticky"
>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
{packageInfo && (formState === 'INVALID' || hasAgentPolicyError) ? (

View file

@ -13,7 +13,8 @@ export type EditPackagePolicyFrom =
| 'edit'
| 'upgrade-from-fleet-policy-list'
| 'upgrade-from-integrations-policy-list'
| 'upgrade-from-extension';
| 'upgrade-from-extension'
| 'onboarding-integration';
export type PackagePolicyFormState =
| 'VALID'
@ -35,5 +36,8 @@ export interface AddToPolicyParams {
export type CreatePackagePolicyParams = React.FunctionComponent<{
from: EditPackagePolicyFrom;
queryParamsPolicyId?: string;
propPolicyId?: string;
integrationName?: string;
prerelease: boolean;
onNext?: () => void;
}>;

View file

@ -38,7 +38,11 @@ import { INTEGRATIONS_ROUTING_PATHS, pagePathGetters } from './constants';
import type { UIExtensionsStorage } from './types';
import { EPMApp } from './sections/epm';
import { PackageInstallProvider, UIExtensionsContext, FlyoutContextProvider } from './hooks';
import {
PackageInstallProvider,
UIExtensionsContextProvider,
FlyoutContextProvider,
} from './hooks';
import { IntegrationsHeader } from './components/header';
import { AgentEnrollmentFlyout } from './components';
import { ReadOnlyContextProvider } from './hooks/use_read_only_context';
@ -103,7 +107,7 @@ export const IntegrationsAppContext: React.FC<{
<EuiThemeProvider darkMode={isDarkMode}>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<UIExtensionsContext.Provider value={extensions}>
<UIExtensionsContextProvider values={extensions}>
<FleetStatusProvider defaultFleetStatus={fleetStatus}>
<SpaceSettingsContextProvider>
<startServices.customIntegrations.ContextProvider>
@ -126,7 +130,7 @@ export const IntegrationsAppContext: React.FC<{
</startServices.customIntegrations.ContextProvider>
</SpaceSettingsContextProvider>
</FleetStatusProvider>
</UIExtensionsContext.Provider>
</UIExtensionsContextProvider>
</QueryClientProvider>
</EuiThemeProvider>
</KibanaVersionContext.Provider>

View file

@ -0,0 +1,58 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { createContext } from 'react';
import {
FleetStatusProvider,
KibanaVersionContext,
UIExtensionsContextProvider,
} from '../../../hooks';
import type { FleetStartServices } from '../../../plugin';
import { PackageInstallProvider } from './use_package_install';
import { IntegrationsStateContextProvider } from './use_integrations_state';
interface FleetIntegrationsStateContextValue {
pkgkey: string | undefined;
startServices: FleetStartServices | undefined;
}
const FleetIntegrationsStateContext = createContext<FleetIntegrationsStateContextValue>({
pkgkey: undefined,
startServices: undefined,
});
export const FleetIntegrationsStateContextProvider: React.FC<{
children?: React.ReactNode;
values: FleetIntegrationsStateContextValue;
/* fix hard coded KibanaVersion */
}> = ({ children, values: { startServices, kibanaVersion = '8.16.0' } }) => {
return (
<FleetIntegrationsStateContext.Provider value={{ fleet: startServices.fleet }}>
<KibanaVersionContext.Provider value={kibanaVersion}>
<UIExtensionsContextProvider values={{}}>
<FleetStatusProvider>
<PackageInstallProvider startServices={startServices}>
<IntegrationsStateContextProvider>{children}</IntegrationsStateContextProvider>
</PackageInstallProvider>
</FleetStatusProvider>
</UIExtensionsContextProvider>
</KibanaVersionContext.Provider>
</FleetIntegrationsStateContext.Provider>
);
};
export const useFleetIntegrationsStateContext = () => {
const ctx = React.useContext(FleetIntegrationsStateContext);
if (!ctx) {
throw new Error(
'useFleetIntegrationsStateContext can only be used inside of FleetIntegrationsStateContextProvider'
);
}
return ctx;
};

View file

@ -23,13 +23,19 @@ export const IntegrationsStateContextProvider: FunctionComponent<{
children?: React.ReactNode;
}> = ({ children }) => {
const maybeState = useIntraAppState<undefined | IntegrationsAppBrowseRouteState>();
const fromIntegrationsRef = useRef<undefined | string>(maybeState?.fromIntegrations);
const stateRef = useRef(maybeState);
console.log('myState---', maybeState);
const getFromIntegrations = useCallback(() => {
return fromIntegrationsRef.current;
return stateRef.current?.fromIntegrations;
}, []);
return (
<IntegrationsStateContext.Provider value={{ getFromIntegrations }}>
<IntegrationsStateContext.Provider
value={{
getFromIntegrations,
pkgkey: maybeState?.pkgkey,
panel: maybeState?.panel,
}}
>
{children}
</IntegrationsStateContext.Provider>
);

View file

@ -261,16 +261,18 @@ function usePackageInstall({ startServices }: { startServices: StartServices })
};
}
export const [
PackageInstallProvider,
useInstallPackage,
useSetPackageInstallStatus,
useGetPackageInstallStatus,
useUninstallPackage,
] = createContainer(
export const packageInstallContainer = createContainer(
usePackageInstall,
(value) => value.installPackage,
(value) => value.setPackageInstallStatus,
(value) => value.getPackageInstallStatus,
(value) => value.uninstallPackage
);
export const [
PackageInstallProvider,
useInstallPackage,
useSetPackageInstallStatus,
useGetPackageInstallStatus,
useUninstallPackage,
] = packageInstallContainer;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButtonWithTooltip } from '../../../../../components';
@ -14,8 +14,8 @@ interface AddIntegrationButtonProps {
userCanInstallPackages?: boolean;
missingSecurityConfiguration: boolean;
packageName: string;
href: string;
onClick: Function;
href: string | undefined;
onClick: Function | undefined;
}
export function AddIntegrationButton(props: AddIntegrationButtonProps) {
@ -38,15 +38,22 @@ export function AddIntegrationButton(props: AddIntegrationButtonProps) {
}
: undefined;
const optionalProps = useMemo(
() => ({
...(href ? { href } : {}),
...(onClick ? { onClick: (e: React.MouseEvent) => onClick(e) } : {}),
}),
[href, onClick]
);
return (
<EuiButtonWithTooltip
fill
isDisabled={!userCanInstallPackages}
iconType="plusInCircle"
href={href}
onClick={(e) => onClick(e)}
data-test-subj="addIntegrationPolicyButton"
tooltip={tooltip}
{...optionalProps}
>
<FormattedMessage
id="xpack.fleet.epm.addPackagePolicyButtonText"

View file

@ -91,6 +91,7 @@ import { Configs } from './configs';
import './index.scss';
import type { InstallPkgRouteOptions } from './utils/get_install_route_options';
import { InstallButton } from './settings/install_button';
import { TabsContent } from './tabs_content';
export type DetailViewPanelName =
| 'overview'
@ -129,10 +130,25 @@ function Breadcrumbs({ packageTitle }: { packageTitle: string }) {
return null;
}
export function Detail() {
export function Detail({
originFrom,
routesEnabled = true,
onAddIntegrationPolicyClick,
}: {
originFrom?: string;
routesEnabled?: boolean;
onAddIntegrationPolicyClick?: () => void;
}) {
const { getId: getAgentPolicyId } = useAgentPolicyContext();
const { getFromIntegrations } = useIntegrationsStateContext();
const { pkgkey, panel } = useParams<DetailParams>();
const {
getFromIntegrations,
pkgkey: pkgKeyContext,
panel: panelContext,
} = useIntegrationsStateContext();
const [selectedPanel, setSelectedPanel] = useState<DetailViewPanelName>(panelContext);
const { pkgkey: pkgkeyParam, panel: panelParam } = useParams<DetailParams>();
const pkgkey = pkgkeyParam || pkgKeyContext;
const panel = panelParam || selectedPanel;
const { getHref, getPath } = useLink();
const history = useHistory();
const { pathname, search, hash } = useLocation();
@ -144,7 +160,6 @@ export function Detail() {
*/
const onboardingLink = useMemo(() => queryParams.get('onboardingLink'), [queryParams]);
const onboardingAppId = useMemo(() => queryParams.get('onboardingAppId'), [queryParams]);
const authz = useAuthz();
const canAddAgent = authz.fleet.addAgents;
const canInstallPackages = authz.integrations.installPackages;
@ -178,7 +193,7 @@ export function Detail() {
if (packageInfo === null || !packageInfo.name) {
return undefined;
}
return getPackageInstallStatus(packageInfo?.name)?.status;
return getPackageInstallStatus?.(packageInfo?.name)?.status;
}, [packageInfo, getPackageInstallStatus]);
const isInstalled = useMemo(
() =>
@ -401,6 +416,11 @@ export function Detail() {
ev.preventDefault();
// The object below, given to `createHref` is explicitly accessing keys of `location` in order
// to ensure that dependencies to this `useCallback` is set correctly (because `location` is mutable)
if (onAddIntegrationPolicyClick) {
onAddIntegrationPolicyClick();
return;
}
const currentPath = history.createHref({
pathname,
search,
@ -448,6 +468,7 @@ export function Detail() {
isExperimentalAddIntegrationPageEnabled,
isFirstTimeAgentUser,
isGuidedOnboardingActive,
onAddIntegrationPolicyClick,
onboardingAppId,
onboardingLink,
pathname,
@ -559,13 +580,17 @@ export function Detail() {
>
<AddIntegrationButton
userCanInstallPackages={userCanInstallPackages}
href={getHref('add_integration_to_policy', {
pkgkey,
...(integration ? { integration } : {}),
...(agentPolicyIdFromContext
? { agentPolicyId: agentPolicyIdFromContext }
: {}),
})}
href={
onAddIntegrationPolicyClick
? undefined
: getHref('add_integration_to_policy', {
pkgkey,
...(integration ? { integration } : {}),
...(agentPolicyIdFromContext
? { agentPolicyId: agentPolicyIdFromContext }
: {}),
})
}
missingSecurityConfiguration={missingSecurityConfiguration}
packageName={integrationInfo?.title || packageInfo.title}
onClick={handleAddIntegrationPolicyClick}
@ -593,12 +618,16 @@ export function Detail() {
) : undefined,
[
packageInfo,
showVersionSelect,
versionLabel,
versionOptions,
updateAvailable,
isInstalled,
pkgkey,
isOverviewPage,
isGuidedOnboardingActive,
userCanInstallPackages,
onAddIntegrationPolicyClick,
getHref,
integration,
agentPolicyIdFromContext,
@ -606,9 +635,6 @@ export function Detail() {
integrationInfo?.title,
handleAddIntegrationPolicyClick,
onVersionChange,
showVersionSelect,
versionLabel,
versionOptions,
]
);
@ -629,10 +655,17 @@ export function Detail() {
),
isSelected: panel === 'overview',
'data-test-subj': `tab-overview`,
href: getHref('integration_details_overview', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href:
originFrom !== 'onboarding-integration'
? getHref('integration_details_overview', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('overview');
},
},
];
@ -647,10 +680,16 @@ export function Detail() {
),
isSelected: panel === 'policies',
'data-test-subj': `tab-policies`,
href: getHref('integration_details_policies', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href: routesEnabled
? getHref('integration_details_policies', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('policies');
},
});
}
@ -671,10 +710,16 @@ export function Detail() {
),
isSelected: panel === 'assets',
'data-test-subj': `tab-assets`,
href: getHref('integration_details_assets', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href: routesEnabled
? getHref('integration_details_assets', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('assets');
},
});
}
@ -689,10 +734,16 @@ export function Detail() {
),
isSelected: panel === 'settings',
'data-test-subj': `tab-settings`,
href: getHref('integration_details_settings', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href: routesEnabled
? getHref('integration_details_settings', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('settings');
},
});
}
@ -707,10 +758,16 @@ export function Detail() {
),
isSelected: panel === 'configs',
'data-test-subj': `tab-configs`,
href: getHref('integration_details_configs', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href: routesEnabled
? getHref('integration_details_configs', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('configs');
},
});
}
@ -725,10 +782,16 @@ export function Detail() {
),
isSelected: panel === 'custom',
'data-test-subj': `tab-custom`,
href: getHref('integration_details_custom', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href: routesEnabled
? getHref('integration_details_custom', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('custom');
},
});
}
@ -743,10 +806,16 @@ export function Detail() {
),
isSelected: panel === 'api-reference',
'data-test-subj': `tab-api-reference`,
href: getHref('integration_details_api_reference', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
}),
href: routesEnabled
? getHref('integration_details_api_reference', {
pkgkey: packageInfoKey,
...(integration ? { integration } : {}),
})
: undefined,
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
setSelectedPanel('api-reference');
},
});
}
@ -754,6 +823,7 @@ export function Detail() {
}, [
packageInfo,
panel,
originFrom,
getHref,
integration,
canReadIntegrationPolicies,
@ -763,6 +833,7 @@ export function Detail() {
showConfigTab,
showCustomTab,
showDocumentationTab,
routesEnabled,
numOfDeferredInstallations,
]);
@ -823,45 +894,17 @@ export function Detail() {
) : isLoading || !packageInfo ? (
<Loading />
) : (
<Routes>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_overview}>
<OverviewPage
packageInfo={packageInfo}
integrationInfo={integrationInfo}
latestGAVersion={latestGAVersion}
/>
</Route>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_settings}>
<SettingsPage
packageInfo={packageInfo}
packageMetadata={packageInfoData?.metadata}
startServices={services}
/>
</Route>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_assets}>
<AssetsPage packageInfo={packageInfo} refetchPackageInfo={refetchPackageInfo} />
</Route>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_configs}>
<Configs packageInfo={packageInfo} />
</Route>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_policies}>
{canReadIntegrationPolicies ? (
<PackagePoliciesPage packageInfo={packageInfo} />
) : (
<PermissionsError
error="MISSING_PRIVILEGES"
requiredFleetRole="Agent Policies Read and Integrations Read"
/>
)}
</Route>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_custom}>
<CustomViewPage packageInfo={packageInfo} />
</Route>
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_api_reference}>
<DocumentationPage packageInfo={packageInfo} integration={integrationInfo?.name} />
</Route>
<Redirect to={INTEGRATIONS_ROUTING_PATHS.integration_details_overview} />
</Routes>
<TabsContent
canReadIntegrationPolicies={canReadIntegrationPolicies}
integrationInfo={integrationInfo}
latestGAVersion={latestGAVersion}
packageInfo={packageInfo}
packageInfoData={packageInfoData}
panel={panel}
refetchPackageInfo={refetchPackageInfo}
routesEnabled={routesEnabled}
services={services}
/>
)}
</WithHeaderLayout>
);

View file

@ -12,7 +12,12 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { InstallStatus } from '../../../../../types';
import type { PackageInfo } from '../../../../../types';
import { useAuthz, useGetPackageInstallStatus, useUninstallPackage } from '../../../../../hooks';
import {
useAuthz,
useGetPackageInstallStatus,
useIntegrationsStateContext,
useUninstallPackage,
} from '../../../../../hooks';
import { ConfirmPackageUninstall } from './confirm_package_uninstall';

View file

@ -0,0 +1,112 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { Routes, Route } from '@kbn/shared-ux-router';
import { Redirect } from 'react-router-dom';
import { INTEGRATIONS_ROUTING_PATHS } from '../../../../constants';
import { PermissionsError } from '../../../../../fleet/layouts';
import type { FleetStart, FleetStartServices } from '../../../../../../plugin';
import { AssetsPage } from './assets';
import { OverviewPage } from './overview';
import { PackagePoliciesPage } from './policies';
import { SettingsPage } from './settings';
import { CustomViewPage } from './custom';
import { DocumentationPage } from './documentation';
import { Configs } from './configs';
export const TabsContent: React.FC<{
canReadIntegrationPolicies: boolean;
integrationInfo: any;
latestGAVersion: string | undefined;
packageInfo: any;
packageInfoData: any;
panel: string;
refetchPackageInfo: () => void;
routesEnabled: boolean;
services: FleetStartServices & {
fleet?: FleetStart | undefined;
};
}> = ({
canReadIntegrationPolicies,
integrationInfo,
latestGAVersion,
packageInfo,
packageInfoData,
panel,
refetchPackageInfo,
routesEnabled,
services,
}) => {
const routesMap = {
overview: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_overview,
component: (
<OverviewPage
packageInfo={packageInfo}
integrationInfo={integrationInfo}
latestGAVersion={latestGAVersion}
/>
),
},
settings: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_settings,
component: (
<SettingsPage
packageInfo={packageInfo}
packageMetadata={packageInfoData?.metadata}
startServices={services}
/>
),
},
assets: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_assets,
component: <AssetsPage packageInfo={packageInfo} refetchPackageInfo={refetchPackageInfo} />,
},
configs: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_configs,
component: <Configs packageInfo={packageInfo} />,
},
policies: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_policies,
component: canReadIntegrationPolicies ? (
<PackagePoliciesPage name={packageInfo.name} version={packageInfo.version} />
) : (
<PermissionsError
error="MISSING_PRIVILEGES"
requiredFleetRole="Agent Policies Read and Integrations Read"
/>
),
},
custom: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_custom,
component: <CustomViewPage packageInfo={packageInfo} />,
},
apiReference: {
path: INTEGRATIONS_ROUTING_PATHS.integration_details_api_reference,
component: (
<DocumentationPage packageInfo={packageInfo} integration={integrationInfo?.name} />
),
},
};
return routesEnabled ? (
<Routes>
{Object.values(routesMap).map(({ path, component }) => (
<Route key={path} path={path}>
{component}
</Route>
))}
<Redirect to={INTEGRATIONS_ROUTING_PATHS.integration_details_overview} />
</Routes>
) : (
routesMap[panel].component
);
};
TabsContent.displayName = 'TabsRoute';

View file

@ -66,6 +66,7 @@ export interface IntegrationCardItem {
url: string;
version: string;
type?: string;
pkgkey?: string;
}
export const mapToCard = ({
@ -91,6 +92,9 @@ export const mapToCard = ({
let isUpdateAvailable = false;
let isReauthorizationRequired = false;
const pkgkey = item.name ? `${item.name}-${version}` : undefined;
if (item.type === 'ui_link') {
uiInternalPathUrl = item.id.includes('language_client.')
? addBasePath(item.uiInternalPath)
@ -103,9 +107,8 @@ export const mapToCard = ({
isReauthorizationRequired = hasDeferredInstallations(item);
}
const url = getHref('integration_details_overview', {
pkgkey: `${item.name}-${version}`,
pkgkey,
...(item.integration ? { integration: item.integration } : {}),
});
@ -136,6 +139,7 @@ export const mapToCard = ({
isUnverified,
isUpdateAvailable,
extraLabelsBadges,
pkgkey,
};
if (item.type === 'integration') {

View file

@ -116,9 +116,12 @@ export const ConfirmAgentEnrollment: React.FunctionComponent<Props> = ({
);
const onButtonClick = () => {
if (onClickViewAgents) onClickViewAgents();
const href = getHref('agent_list');
application.navigateToUrl(href);
if (onClickViewAgents) {
onClickViewAgents();
} else {
const href = getHref('agent_list');
application.navigateToUrl(href);
}
};
if (!policyId || (agentCount === 0 && !showLoading)) {

View file

@ -5,10 +5,13 @@
* 2.0.
*/
import { useFleetIntegrationsStateContext } from '../applications/integrations/hooks/use_fleet_integration_context';
import { useStartServices } from './use_core';
// Expose authz object, containing the privileges for Fleet and Integrations
export function useAuthz() {
const core = useStartServices();
return core.authz;
const { fleet } = useFleetIntegrationsStateContext();
return core.authz ?? fleet.authz;
}

View file

@ -16,6 +16,5 @@ import type { AnyIntraAppRouteState } from '../types';
*/
export function useIntraAppState<S = AnyIntraAppRouteState>(): S | undefined {
const location = useLocation();
return location.state as S;
}

View file

@ -11,6 +11,16 @@ import type { UIExtensionPoint, UIExtensionsStorage } from '../types';
export const UIExtensionsContext = React.createContext<UIExtensionsStorage>({});
export const UIExtensionsContextProvider = ({
values,
children,
}: {
values: UIExtensionsStorage;
children: React.ReactNode;
}) => {
return <UIExtensionsContext.Provider value={values}>{children}</UIExtensionsContext.Provider>;
};
type NarrowExtensionPoint<V extends UIExtensionPoint['view'], A = UIExtensionPoint> = A extends {
view: V;
}

View file

@ -10,6 +10,7 @@ import type { PluginInitializerContext } from '@kbn/core/public';
import { lazy } from 'react';
import { FleetPlugin } from './plugin';
import type { UIExtensionsStorage } from './types';
export type { GetPackagesResponse } from './types';
export { installationStatuses } from '../common/constants';
@ -89,3 +90,17 @@ export const AvailablePackagesHook = () => {
'./applications/integrations/sections/epm/screens/home/hooks/use_available_packages'
);
};
export const Detail = () => {
return import('./applications/integrations/sections/epm/screens/detail');
};
export const UseIntegrationsState = () => {
return import('./applications/integrations/hooks/use_integrations_state');
};
export const CreatePackagePolicyPage = () => {
return import('./applications/fleet/sections/agent_policy/create_package_policy_page');
};
export const FleetIntegrationsStateContextProvider = () => {
return import('./applications/integrations/hooks/use_fleet_integration_context');
};

View file

@ -20,12 +20,13 @@ import { OnboardingContextProvider } from './onboarding_context';
import { OnboardingAVCBanner } from './onboarding_banner';
import { OnboardingRoute } from './onboarding_route';
import { OnboardingFooter } from './onboarding_footer';
import type { StartPlugins } from '../../types';
const topicPathParam = `:topicId(${Object.values(OnboardingTopicId) // any topics
.filter((val) => val !== OnboardingTopicId.default) // except "default"
.join('|')})?`; // optional parameter
export const OnboardingPage = React.memo(() => {
export const OnboardingPage = React.memo((plugins: StartPlugins) => {
const spaceId = useSpaceId();
const { euiTheme } = useEuiTheme();
@ -38,7 +39,7 @@ export const OnboardingPage = React.memo(() => {
}
return (
<OnboardingContextProvider spaceId={spaceId}>
<OnboardingContextProvider spaceId={spaceId} fleet={plugins.fleet}>
<PluginTemplateWrapper paddingSize="none" data-test-subj="onboarding-hub-page">
<OnboardingAVCBanner />
<KibanaPageTemplate.Section

View file

@ -4,12 +4,26 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { lazy, Suspense, useMemo, useCallback, useEffect, useRef } from 'react';
import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiSkeletonText } from '@elastic/eui';
import React, { lazy, Suspense, useMemo, useCallback, useEffect, useRef, useState } from 'react';
import {
EuiButton,
EuiButtonGroup,
EuiFlexGroup,
EuiFlexItem,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiPortal,
EuiSkeletonText,
useGeneratedHtmlId,
} from '@elastic/eui';
import type { AvailablePackagesHookType, IntegrationCardItem } from '@kbn/fleet-plugin/public';
import { noop } from 'lodash';
import { css } from '@emotion/react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { withLazyHook } from '../../../../../common/components/with_lazy_hook';
import {
useStoredIntegrationSearchTerm,
@ -45,9 +59,37 @@ export const PackageListGrid = lazy(async () => ({
.then((pkg) => pkg.PackageListGrid),
}));
const Detail = lazy(async () => ({
default: await import('@kbn/fleet-plugin/public')
.then((module) => module.Detail())
.then((pkg) => pkg.Detail),
}));
const CreatePackagePolicyPage = lazy(async () => ({
default: await import('@kbn/fleet-plugin/public')
.then((module) => module.CreatePackagePolicyPage())
.then((pkg) => pkg.CreatePackagePolicyPage),
}));
const FleetIntegrationsStateContextProvider = lazy(async () => ({
default: await import('@kbn/fleet-plugin/public')
.then((module) => module.FleetIntegrationsStateContextProvider())
.then((pkg) => pkg.FleetIntegrationsStateContextProvider),
}));
const integrationStepMap = {
0: 'Add integration',
1: 'Install Elastic Agent',
2: 'Confirm incoming data',
}
export const IntegrationsCardGridTabsComponent = React.memo<IntegrationsCardGridTabsProps>(
({ installedIntegrationsCount, isAgentRequired, useAvailablePackages }) => {
const { spaceId } = useOnboardingContext();
const startServices = useKibana().services;
const {
services: { fleet },
} = useKibana();
const scrollElement = useRef<HTMLDivElement>(null);
const [toggleIdSelected, setSelectedTabIdToStorage] = useStoredIntegrationTabId(
spaceId,
@ -65,6 +107,25 @@ export const IntegrationsCardGridTabsComponent = React.memo<IntegrationsCardGrid
[setSelectedTabIdToStorage]
);
const [isModalVisible, setIsModalVisible] = useState(false);
const [integrationName, setIntegrationName] = useState();
const [modalView, setModalView] = useState<'overview' | 'configure-integration' | 'add-agent'>(
'overview'
);
const [integrationStep, setIntegrationStep] = useState(0);
const onAddIntegrationPolicyClick = useCallback(() => {
setModalView('configure-integration');
}, []);
const closeModal = useCallback(() => {
setIsModalVisible(false);
setModalView('overview');
setIntegrationStep(0);
}, []);
const onCardClicked = useCallback((name: string) => {
setIsModalVisible(true);
setIntegrationName(name);
}, []);
const modalTitleId = useGeneratedHtmlId();
const {
filteredCards,
isLoading,
@ -77,7 +138,6 @@ export const IntegrationsCardGridTabsComponent = React.memo<IntegrationsCardGrid
});
const selectedTab = useMemo(() => INTEGRATION_TABS_BY_ID[toggleIdSelected], [toggleIdSelected]);
const onSearchTermChanged = useCallback(
(searchQuery: string) => {
setSearchTerm(searchQuery);
@ -116,10 +176,10 @@ export const IntegrationsCardGridTabsComponent = React.memo<IntegrationsCardGrid
setSelectedSubCategory,
toggleIdSelected,
]);
const list: IntegrationCardItem[] = useIntegrationCardList({
integrationsList: filteredCards,
featuredCardIds: selectedTab.featuredCardIds,
onCardClicked,
});
if (isLoading) {
@ -132,68 +192,108 @@ export const IntegrationsCardGridTabsComponent = React.memo<IntegrationsCardGrid
);
}
return (
<EuiFlexGroup
direction="column"
className="step-paragraph"
gutterSize={selectedTab.showSearchTools ? 'm' : 'none'}
css={css`
height: ${selectedTab.showSearchTools
? WITH_SEARCH_BOX_HEIGHT
: WITHOUT_SEARCH_BOX_HEIGHT};
`}
>
<EuiFlexItem grow={false}>
<EuiButtonGroup
buttonSize="compressed"
color="primary"
idSelected={toggleIdSelected}
isFullWidth
legend="Categories"
onChange={onTabChange}
options={INTEGRATION_TABS}
type="single"
/>
</EuiFlexItem>
<EuiFlexItem
<>
<EuiFlexGroup
direction="column"
className="step-paragraph"
gutterSize={selectedTab.showSearchTools ? 'm' : 'none'}
css={css`
overflow-y: ${selectedTab.overflow ?? 'auto'};
height: ${selectedTab.showSearchTools
? WITH_SEARCH_BOX_HEIGHT
: WITHOUT_SEARCH_BOX_HEIGHT};
`}
grow={1}
id={SCROLL_ELEMENT_ID}
ref={scrollElement}
>
<Suspense
fallback={<EuiSkeletonText isLoading={true} lines={LOADING_SKELETON_TEXT_LINES} />}
>
<PackageListGrid
callout={
<IntegrationCardTopCallout
isAgentRequired={isAgentRequired}
installedIntegrationsCount={installedIntegrationsCount}
selectedTabId={toggleIdSelected}
/>
}
calloutTopSpacerSize="m"
categories={SEARCH_FILTER_CATEGORIES} // We do not want to show categories and subcategories as the search bar filter
emptyStateStyles={emptyStateStyles}
list={list}
scrollElementId={SCROLL_ELEMENT_ID}
searchTerm={searchTerm}
selectedCategory={selectedTab.category ?? ''}
selectedSubCategory={selectedTab.subCategory}
setCategory={setCategory}
setSearchTerm={onSearchTermChanged}
setUrlandPushHistory={noop}
setUrlandReplaceHistory={noop}
showCardLabels={false}
showControls={false}
showSearchTools={selectedTab.showSearchTools}
sortByFeaturedIntegrations={selectedTab.sortByFeaturedIntegrations}
spacer={false}
<EuiFlexItem grow={false}>
<EuiButtonGroup
buttonSize="compressed"
color="primary"
idSelected={toggleIdSelected}
isFullWidth
legend="Categories"
onChange={onTabChange}
options={INTEGRATION_TABS}
type="single"
/>
</Suspense>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem
css={css`
overflow-y: ${selectedTab.overflow ?? 'auto'};
`}
grow={1}
id={SCROLL_ELEMENT_ID}
ref={scrollElement}
>
<Suspense
fallback={<EuiSkeletonText isLoading={true} lines={LOADING_SKELETON_TEXT_LINES} />}
>
<PackageListGrid
callout={
<IntegrationCardTopCallout
isAgentRequired={isAgentRequired}
installedIntegrationsCount={installedIntegrationsCount}
selectedTabId={toggleIdSelected}
/>
}
calloutTopSpacerSize="m"
categories={SEARCH_FILTER_CATEGORIES} // We do not want to show categories and subcategories as the search bar filter
emptyStateStyles={emptyStateStyles}
list={list}
scrollElementId={SCROLL_ELEMENT_ID}
searchTerm={searchTerm}
selectedCategory={selectedTab.category ?? ''}
selectedSubCategory={selectedTab.subCategory}
setCategory={setCategory}
setSearchTerm={onSearchTermChanged}
setUrlandPushHistory={noop}
setUrlandReplaceHistory={noop}
showCardLabels={false}
showControls={false}
showSearchTools={selectedTab.showSearchTools}
sortByFeaturedIntegrations={selectedTab.sortByFeaturedIntegrations}
spacer={false}
/>
</Suspense>
</EuiFlexItem>
</EuiFlexGroup>
{isModalVisible && fleet && (
<EuiPortal>
<EuiModal
aria-labelledby={modalTitleId}
onClose={closeModal}
css={css`
width: 85%;
`}
maxWidth="90%"
>
{modalView === 'configure-integration' && (<EuiModalHeader>{`step indicator place holder. Integration step: ${integrationStepMap[integrationStep]}`}</EuiModalHeader>)}
<EuiModalBody>
<FleetIntegrationsStateContextProvider
values={{ startServices, useMultiPageLayoutProp: true }}
>
{modalView === 'overview' && (
<Detail
onAddIntegrationPolicyClick={onAddIntegrationPolicyClick}
originFrom="onboarding-integration"
routesEnabled={false}
/>
)}
{modalView === 'configure-integration' && (
<CreatePackagePolicyPage
useMultiPageLayoutProp={true}
originFrom="onboarding-integration"
propPolicyId=""
integrationName={integrationName}
setIntegrationStep={setIntegrationStep}
onCanceled={closeModal}
/>
)}
</FleetIntegrationsStateContextProvider>
</EuiModalBody>
{/* <EuiModalFooter><EuiButton onClick={closeModal}>Close</EuiButton></EuiModalFooter> */}
</EuiModal>
</EuiPortal>
)}
</>
);
}
);

View file

@ -16,7 +16,6 @@ import {
import {
CARD_DESCRIPTION_LINE_CLAMP,
CARD_TITLE_LINE_CLAMP,
INTEGRATION_APP_ID,
MAX_CARD_HEIGHT_IN_PX,
ONBOARDING_APP_ID,
ONBOARDING_LINK,
@ -50,15 +49,23 @@ const getFilteredCards = ({
installedIntegrationList,
integrationsList,
navigateTo,
onCardClicked,
}: {
featuredCardIds?: string[];
getAppUrl: GetAppUrl;
installedIntegrationList?: IntegrationCardItem[];
integrationsList: IntegrationCardItem[];
navigateTo: NavigateTo;
onCardClicked?: (integrationName: string) => void;
}) => {
const securityIntegrationsList = integrationsList.map((card) =>
addSecuritySpecificProps({ navigateTo, getAppUrl, card, installedIntegrationList })
addSecuritySpecificProps({
navigateTo,
getAppUrl,
card,
installedIntegrationList,
onCardClicked,
})
);
if (!featuredCardIds) {
return { featuredCards: [], integrationCards: securityIntegrationsList };
@ -74,23 +81,32 @@ const addSecuritySpecificProps = ({
navigateTo,
getAppUrl,
card,
onCardClicked,
}: {
navigateTo: NavigateTo;
getAppUrl: GetAppUrl;
card: IntegrationCardItem;
installedIntegrationList?: IntegrationCardItem[];
onCardClicked?: (integrationName: string) => void;
}): IntegrationCardItem => {
const onboardingLink = getAppUrl({ appId: SECURITY_UI_APP_ID, path: ONBOARDING_PATH });
const integrationRootUrl = getAppUrl({ appId: INTEGRATION_APP_ID });
const state = {
onCancelNavigateTo: [APP_UI_ID, { path: ONBOARDING_PATH }],
onCancelUrl: onboardingLink,
onSaveNavigateTo: [APP_UI_ID, { path: ONBOARDING_PATH }],
};
const url =
card.url.indexOf(APP_INTEGRATIONS_PATH) >= 0 && onboardingLink
? addPathParamToUrl(card.url, onboardingLink)
: card.url;
const state = {
onCancelNavigateTo: [
APP_UI_ID,
{ path: ONBOARDING_PATH, state: { pkgkey: card.pkgkey, onCancelUrl: onboardingLink } },
],
onCancelUrl: onboardingLink,
onSaveNavigateTo: [APP_UI_ID, { path: ONBOARDING_PATH, state: { pkgkey: card.pkgkey } }],
pkgkey: card.pkgkey,
panel: 'overview', // Default to the overview tab on modal opened
};
return {
...card,
titleLineClamp: CARD_TITLE_LINE_CLAMP,
@ -101,10 +117,12 @@ const addSecuritySpecificProps = ({
onCardClick: () => {
const trackId = `${TELEMETRY_INTEGRATION_CARD}_${card.id}`;
trackOnboardingLinkClick(trackId);
if (url.startsWith(APP_INTEGRATIONS_PATH)) {
onCardClicked?.(card.name); // fix me: type error
navigateTo({
appId: INTEGRATION_APP_ID,
path: url.slice(integrationRootUrl.length),
path: `${addPathParamToUrl(ONBOARDING_PATH, onboardingLink)}#integrations`,
state,
});
} else if (url.startsWith('http') || url.startsWith('https')) {
@ -119,15 +137,24 @@ const addSecuritySpecificProps = ({
export const useIntegrationCardList = ({
integrationsList,
featuredCardIds,
onCardClicked,
}: {
integrationsList: IntegrationCardItem[];
featuredCardIds?: string[] | undefined;
onCardClicked?: (integrationName: string) => void;
}): IntegrationCardItem[] => {
const { navigateTo, getAppUrl } = useNavigation();
const { featuredCards, integrationCards } = useMemo(
() => getFilteredCards({ navigateTo, getAppUrl, integrationsList, featuredCardIds }),
[navigateTo, getAppUrl, integrationsList, featuredCardIds]
() =>
getFilteredCards({
navigateTo,
getAppUrl,
integrationsList,
featuredCardIds,
onCardClicked,
}),
[navigateTo, getAppUrl, integrationsList, featuredCardIds, onCardClicked]
);
if (featuredCardIds && featuredCardIds.length > 0) {

View file

@ -35,18 +35,19 @@ export interface OnboardingContextValue {
}
const OnboardingContext = createContext<OnboardingContextValue | null>(null);
export const OnboardingContextProvider: React.FC<PropsWithChildren<{ spaceId: string }>> =
React.memo(({ children, spaceId }) => {
const config = useFilteredConfig();
const telemetry = useOnboardingTelemetry();
export const OnboardingContextProvider: React.FC<
PropsWithChildren<{ spaceId: string; fleet: FleetStart }>
> = React.memo(({ children, spaceId, fleet }) => {
const config = useFilteredConfig();
const telemetry = useOnboardingTelemetry();
const value = useMemo<OnboardingContextValue>(
() => ({ spaceId, telemetry, config }),
[spaceId, telemetry, config]
);
const value = useMemo<OnboardingContextValue>(
() => ({ spaceId, telemetry, config }),
[spaceId, telemetry, config]
);
return <OnboardingContext.Provider value={value}>{children}</OnboardingContext.Provider>;
});
return <OnboardingContext.Provider value={value}>{children}</OnboardingContext.Provider>;
});
OnboardingContextProvider.displayName = 'OnboardingContextProvider';
export const useOnboardingContext = () => {

View file

@ -6,14 +6,15 @@
*/
import type { SecuritySubPlugin } from '../app/types';
import type { StartPlugins } from '../types';
import { routes } from './routes';
export class Onboarding {
public setup() {}
public start(): SecuritySubPlugin {
public start(plugins: StartPlugins): SecuritySubPlugin {
return {
routes,
routes: routes(plugins),
};
}
}

View file

@ -8,9 +8,10 @@
import { ONBOARDING_PATH, SecurityPageName } from '../../common/constants';
import type { SecuritySubPluginRoutes } from '../app/types';
import { withSecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper';
import type { StartPlugins } from '../types';
import { OnboardingPage } from './components/onboarding';
export const routes: SecuritySubPluginRoutes = [
export const routes: (plugins: StartPlugins) => SecuritySubPluginRoutes = (plugins) => [
{
path: ONBOARDING_PATH,
component: withSecurityRoutePageWrapper(OnboardingPage, SecurityPageName.landing),

View file

@ -61,7 +61,7 @@ const createMultipassVm = async ({
log.info(`Creating VM [${name}] using multipass`);
const createResponse = await execa.command(
`multipass launch --name ${name} --disk ${disk} --cpus ${cpus} --memory ${memory}`
`multipass launch --name ${name} --disk ${disk} --cpus ${cpus} --memory ${memory} --network en0`
);
log.verbose(`VM [${name}] created successfully using multipass.`, createResponse);