From 254dc175aeb7519345b412286f60cb14e94ef7fe Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 12 Nov 2024 16:31:47 +0000 Subject: [PATCH] tabs content --- .../fleet/public/applications/fleet/app.tsx | 2 +- .../public/applications/integrations/app.tsx | 10 +- .../hooks/use_integrations_state.tsx | 15 +- .../hooks/use_package_install.tsx | 16 +- .../hooks/use_is_first_time_agent_user.ts | 7 +- .../sections/epm/screens/detail/index.tsx | 168 ++++++++------ .../detail/settings/uninstall_button.tsx | 7 +- .../epm/screens/detail/tabs_content.tsx | 112 ++++++++++ .../sections/epm/screens/home/card_utils.tsx | 8 +- .../plugins/fleet/public/hooks/use_authz.ts | 5 +- .../public/hooks/use_intra_app_state.tsx | 1 - ...e_ui_extension.ts => use_ui_extension.tsx} | 10 + x-pack/plugins/fleet/public/index.ts | 19 ++ .../onboarding/components/onboarding.tsx | 5 +- .../integration_card_grid_tabs.tsx | 208 +++++++++++++----- .../integrations/use_integration_card_list.ts | 71 +++++- .../components/onboarding_context.tsx | 59 ++--- .../public/onboarding/index.ts | 5 +- .../public/onboarding/routes.tsx | 3 +- .../scripts/endpoint/common/vm_services.ts | 2 +- 20 files changed, 530 insertions(+), 203 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/tabs_content.tsx rename x-pack/plugins/fleet/public/hooks/{use_ui_extension.ts => use_ui_extension.tsx} (82%) diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 8a4edef8dc87c..d015fe0e15108 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -212,7 +212,7 @@ export const FleetAppContext: React.FC<{ - + diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index 8527dbc4c6c69..c2f310ec267cf 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -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<{ - + @@ -126,7 +130,7 @@ export const IntegrationsAppContext: React.FC<{ - + diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_integrations_state.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_integrations_state.tsx index 9e4241a6e7f72..0dd7466e62cda 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_integrations_state.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_integrations_state.tsx @@ -23,13 +23,20 @@ export const IntegrationsStateContextProvider: FunctionComponent<{ children?: React.ReactNode; }> = ({ children }) => { const maybeState = useIntraAppState(); - const fromIntegrationsRef = useRef(maybeState?.fromIntegrations); - + const stateRef = useRef(maybeState); + console.log('myState---', maybeState); const getFromIntegrations = useCallback(() => { - return fromIntegrationsRef.current; + return stateRef.current?.fromIntegrations; }, []); return ( - + {children} ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx index 579a711a36398..e7226c23d9060 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_package_install.tsx @@ -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; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts index d57b512726449..86c5f544e7e6a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_first_time_agent_user.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { FleetAuthz } from '../../../../../../../../common'; import { FLEET_SERVER_PACKAGE, LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE, @@ -16,8 +17,10 @@ interface UseIsFirstTimeAgentUserResponse { isLoading?: boolean; } -export const useIsFirstTimeAgentUserQuery = (): UseIsFirstTimeAgentUserResponse => { - const authz = useAuthz(); +export const useIsFirstTimeAgentUserQuery = ( + authzContext?: FleetAuthz +): UseIsFirstTimeAgentUserResponse => { + const authz = useAuthz() ?? authzContext; const { data: packagePolicies, isLoading: areAgentPoliciesLoading, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 9a707500bb03d..825b870f48aca 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -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,17 @@ function Breadcrumbs({ packageTitle }: { packageTitle: string }) { return null; } -export function Detail() { +export function Detail({ routesEnabled = true }: { routesEnabled?: boolean }) { const { getId: getAgentPolicyId } = useAgentPolicyContext(); - const { getFromIntegrations } = useIntegrationsStateContext(); - const { pkgkey, panel } = useParams(); + const { + getFromIntegrations, + pkgkey: pkgKeyContext, + panel: panelContext, + } = useIntegrationsStateContext(); + const [selectedPanel, setSelectedPanel] = useState(panelContext); + const { pkgkey: pkgkeyParam, panel: panelParam } = useParams(); + const pkgkey = pkgkeyParam || pkgKeyContext; + const panel = panelParam || selectedPanel; const { getHref, getPath } = useLink(); const history = useHistory(); const { pathname, search, hash } = useLocation(); @@ -144,7 +152,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 +185,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( () => @@ -253,7 +260,7 @@ export function Detail() { }, [packageInfoLatestPrereleaseData?.item.version]); const { isFirstTimeAgentUser = false, isLoading: firstTimeUserLoading } = - useIsFirstTimeAgentUserQuery(); + useIsFirstTimeAgentUserQuery(authz); const isGuidedOnboardingActive = useIsGuidedOnboardingActive(pkgName); // Refresh package info when status change @@ -629,10 +636,16 @@ export function Detail() { ), isSelected: panel === 'overview', 'data-test-subj': `tab-overview`, - href: getHref('integration_details_overview', { - pkgkey: packageInfoKey, - ...(integration ? { integration } : {}), - }), + href: routesEnabled + ? getHref('integration_details_overview', { + pkgkey: packageInfoKey, + ...(integration ? { integration } : {}), + }) + : undefined, + onClick: (e: React.MouseEvent) => { + e.preventDefault(); + setSelectedPanel('overview'); + }, }, ]; @@ -647,10 +660,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) => { + e.preventDefault(); + setSelectedPanel('policies'); + }, }); } @@ -671,10 +690,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) => { + e.preventDefault(); + setSelectedPanel('assets'); + }, }); } @@ -689,10 +714,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) => { + e.preventDefault(); + setSelectedPanel('settings'); + }, }); } @@ -707,10 +738,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) => { + e.preventDefault(); + setSelectedPanel('configs'); + }, }); } @@ -725,10 +762,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) => { + e.preventDefault(); + setSelectedPanel('custom'); + }, }); } @@ -743,10 +786,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) => { + e.preventDefault(); + setSelectedPanel('api-reference'); + }, }); } @@ -754,6 +803,7 @@ export function Detail() { }, [ packageInfo, panel, + routesEnabled, getHref, integration, canReadIntegrationPolicies, @@ -823,45 +873,17 @@ export function Detail() { ) : isLoading || !packageInfo ? ( ) : ( - - - - - - - - - - - - - - - {canReadIntegrationPolicies ? ( - - ) : ( - - )} - - - - - - - - - + )} ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx index aba40aeba2397..102c95e455069 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/uninstall_button.tsx @@ -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'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/tabs_content.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/tabs_content.tsx new file mode 100644 index 0000000000000..9559a4f969b2f --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/tabs_content.tsx @@ -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: ( + + ), + }, + settings: { + path: INTEGRATIONS_ROUTING_PATHS.integration_details_settings, + component: ( + + ), + }, + assets: { + path: INTEGRATIONS_ROUTING_PATHS.integration_details_assets, + component: , + }, + configs: { + path: INTEGRATIONS_ROUTING_PATHS.integration_details_configs, + component: , + }, + policies: { + path: INTEGRATIONS_ROUTING_PATHS.integration_details_policies, + component: canReadIntegrationPolicies ? ( + + ) : ( + + ), + }, + custom: { + path: INTEGRATIONS_ROUTING_PATHS.integration_details_custom, + component: , + }, + apiReference: { + path: INTEGRATIONS_ROUTING_PATHS.integration_details_api_reference, + component: ( + + ), + }, + }; + return routesEnabled ? ( + + {Object.values(routesMap).map(({ path, component }) => ( + + {component} + + ))} + + + ) : ( + routesMap[panel].component + ); +}; + +TabsContent.displayName = 'TabsRoute'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx index 19f4d8740b75d..260c231995ed7 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/card_utils.tsx @@ -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') { diff --git a/x-pack/plugins/fleet/public/hooks/use_authz.ts b/x-pack/plugins/fleet/public/hooks/use_authz.ts index c7a177c34dce1..40088d086a06e 100644 --- a/x-pack/plugins/fleet/public/hooks/use_authz.ts +++ b/x-pack/plugins/fleet/public/hooks/use_authz.ts @@ -5,10 +5,13 @@ * 2.0. */ +import { useIntegrationsStateContext } from '../applications/integrations/hooks'; + 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 } = useIntegrationsStateContext(); + return core.authz ?? fleet.auth; } diff --git a/x-pack/plugins/fleet/public/hooks/use_intra_app_state.tsx b/x-pack/plugins/fleet/public/hooks/use_intra_app_state.tsx index 265d12221018a..501777bac4d11 100644 --- a/x-pack/plugins/fleet/public/hooks/use_intra_app_state.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_intra_app_state.tsx @@ -16,6 +16,5 @@ import type { AnyIntraAppRouteState } from '../types'; */ export function useIntraAppState(): S | undefined { const location = useLocation(); - return location.state as S; } diff --git a/x-pack/plugins/fleet/public/hooks/use_ui_extension.ts b/x-pack/plugins/fleet/public/hooks/use_ui_extension.tsx similarity index 82% rename from x-pack/plugins/fleet/public/hooks/use_ui_extension.ts rename to x-pack/plugins/fleet/public/hooks/use_ui_extension.tsx index 93e316be241cb..117cb778c21b1 100644 --- a/x-pack/plugins/fleet/public/hooks/use_ui_extension.ts +++ b/x-pack/plugins/fleet/public/hooks/use_ui_extension.tsx @@ -11,6 +11,16 @@ import type { UIExtensionPoint, UIExtensionsStorage } from '../types'; export const UIExtensionsContext = React.createContext({}); +export const UIExtensionsContextProvider = ({ + values, + children, +}: { + values: UIExtensionsStorage; + children: React.ReactNode; +}) => { + return {children}; +}; + type NarrowExtensionPoint = A extends { view: V; } diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index d82e9c88b7db8..9c58978976e3d 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -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,21 @@ 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 UsePackageInstall = () => { + return import('./applications/integrations/hooks/use_package_install'); +}; + +export const FleetStatusProvider = () => { + return import('./hooks/use_fleet_status'); +}; + +export const UIExtensionsContextProvider = () => { + return import('./hooks/use_ui_extension'); +}; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx index 17f4840e68dc4..0b7a08ff94aa8 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding.tsx @@ -18,8 +18,9 @@ import { OnboardingHeader } from './onboarding_header'; import { OnboardingBody } from './onboarding_body'; import { OnboardingFooter } from './onboarding_footer'; import { PAGE_CONTENT_WIDTH } from '../constants'; +import type { StartPlugins } from '../../types'; -export const OnboardingPage = React.memo(() => { +export const OnboardingPage = React.memo((plugins: StartPlugins) => { const spaceId = useSpaceId(); const { euiTheme } = useEuiTheme(); @@ -32,7 +33,7 @@ export const OnboardingPage = React.memo(() => { } return ( - + ({ .then((pkg) => pkg.PackageListGrid), })); +const Detail = lazy(async () => ({ + default: await import('@kbn/fleet-plugin/public') + .then((module) => module.Detail()) + .then((pkg) => pkg.Detail), +})); + +const IntegrationsStateContextProvider = lazy(async () => ({ + default: await import('@kbn/fleet-plugin/public') + .then((module) => module.UseIntegrationsState()) + .then((pkg) => pkg.IntegrationsStateContextProvider), +})); + +const PackageInstallProvider = lazy(async () => ({ + default: await import('@kbn/fleet-plugin/public') + .then((module) => module.UsePackageInstall()) + .then((pkg) => pkg.packageInstallContainer[0]), +})); + +const FleetStatusProvider = lazy(async () => ({ + default: await import('@kbn/fleet-plugin/public') + .then((module) => module.FleetStatusProvider()) + .then((pkg) => pkg.FleetStatusProvider), +})); + +const UIExtensionsContextProvider = lazy(async () => ({ + default: await import('@kbn/fleet-plugin/public') + .then((module) => module.UIExtensionsContextProvider()) + .then((pkg) => pkg.UIExtensionsContextProvider), +})); + export const IntegrationsCardGridTabsComponent = React.memo( ({ installedIntegrationsCount, isAgentRequired, useAvailablePackages }) => { const { spaceId } = useOnboardingContext(); + const startServices = useKibana().services; + const { state: routerState } = useLocation(); const scrollElement = useRef(null); const [toggleIdSelected, setSelectedTabIdToStorage] = useStoredIntegrationTabId( spaceId, @@ -65,6 +116,11 @@ export const IntegrationsCardGridTabsComponent = React.memo setIsModalVisible(false); + const showModal = () => setIsModalVisible(true); + const modalTitleId = useGeneratedHtmlId(); const { filteredCards, isLoading, @@ -116,10 +172,11 @@ export const IntegrationsCardGridTabsComponent = React.memo - - - - + - } - > - - } - 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} + + - - - + + + } + > + + } + 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} + /> + + + + {isModalVisible && startServices && ( + + + + Modal title + + + + + + + + + + + + + + + )} + ); } ); diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/use_integration_card_list.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/use_integration_card_list.ts index ccea5299551c1..61f588cbd00ba 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/use_integration_card_list.ts +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_body/cards/integrations/use_integration_card_list.ts @@ -5,9 +5,9 @@ * 2.0. */ import { useMemo } from 'react'; -import type { IntegrationCardItem } from '@kbn/fleet-plugin/public'; +import type { FleetSetup, FleetStart, IntegrationCardItem } from '@kbn/fleet-plugin/public'; import { SECURITY_UI_APP_ID } from '@kbn/security-solution-navigation'; -import { useNavigation } from '../../../../../common/lib/kibana'; +import { useKibana, useNavigation } from '../../../../../common/lib/kibana'; import { APP_INTEGRATIONS_PATH, APP_UI_ID, @@ -24,6 +24,7 @@ import { } from './constants'; import type { GetAppUrl, NavigateTo } from '../../../../../common/lib/kibana'; import { trackOnboardingLinkClick } from '../../../../common/lib/telemetry'; +import { useOnboardingContext } from '../../../onboarding_context'; const addPathParamToUrl = (url: string, onboardingLink: string) => { const encoded = encodeURIComponent(onboardingLink); @@ -50,15 +51,28 @@ const getFilteredCards = ({ installedIntegrationList, integrationsList, navigateTo, + onCardClicked, + fleet, + application, }: { featuredCardIds?: string[]; getAppUrl: GetAppUrl; installedIntegrationList?: IntegrationCardItem[]; integrationsList: IntegrationCardItem[]; navigateTo: NavigateTo; + onCardClicked?: () => void; + fleet: FleetSetup | undefined; }) => { const securityIntegrationsList = integrationsList.map((card) => - addSecuritySpecificProps({ navigateTo, getAppUrl, card, installedIntegrationList }) + addSecuritySpecificProps({ + navigateTo, + getAppUrl, + card, + installedIntegrationList, + onCardClicked, + fleet, + application, + }) ); if (!featuredCardIds) { return { featuredCards: [], integrationCards: securityIntegrationsList }; @@ -74,23 +88,34 @@ const addSecuritySpecificProps = ({ navigateTo, getAppUrl, card, + onCardClicked, + fleet, + application, }: { navigateTo: NavigateTo; getAppUrl: GetAppUrl; card: IntegrationCardItem; installedIntegrationList?: IntegrationCardItem[]; + onCardClicked?: () => void; + fleet: FleetStart | undefined; }): IntegrationCardItem => { const onboardingLink = getAppUrl({ appId: SECURITY_UI_APP_ID, path: ONBOARDING_PATH }); const integrationRootUrl = getAppUrl({ appId: INTEGRATION_APP_ID }); + + 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 }], onCancelUrl: onboardingLink, onSaveNavigateTo: [APP_UI_ID, { path: ONBOARDING_PATH }], + pkgkey: card.pkgkey, + panel: 'overview', + fleet: { auth: fleet?.authz, config: fleet?.config }, }; - const url = - card.url.indexOf(APP_INTEGRATIONS_PATH) >= 0 && onboardingLink - ? addPathParamToUrl(card.url, onboardingLink) - : card.url; + return { ...card, titleLineClamp: CARD_TITLE_LINE_CLAMP, @@ -101,12 +126,20 @@ const addSecuritySpecificProps = ({ onCardClick: () => { const trackId = `${TELEMETRY_INTEGRATION_CARD}_${card.id}`; trackOnboardingLinkClick(trackId); + if (url.startsWith(APP_INTEGRATIONS_PATH)) { + onCardClicked?.(); + navigateTo({ - appId: INTEGRATION_APP_ID, - path: url.slice(integrationRootUrl.length), + path: `${addPathParamToUrl(ONBOARDING_PATH, onboardingLink)}#integrations`, state, }); + + // navigateTo({ + // appId: INTEGRATION_APP_ID, + // path: url.slice(integrationRootUrl.length), + // state, + // }); } else if (url.startsWith('http') || url.startsWith('https')) { window.open(url, '_blank'); } else { @@ -119,15 +152,29 @@ const addSecuritySpecificProps = ({ export const useIntegrationCardList = ({ integrationsList, featuredCardIds, + onCardClicked, }: { integrationsList: IntegrationCardItem[]; featuredCardIds?: string[] | undefined; + onCardClicked?: () => void; }): IntegrationCardItem[] => { const { navigateTo, getAppUrl } = useNavigation(); - + // const { fleet } = useOnboardingContext(); + const { + services: { application, fleet }, + } = useKibana(); const { featuredCards, integrationCards } = useMemo( - () => getFilteredCards({ navigateTo, getAppUrl, integrationsList, featuredCardIds }), - [navigateTo, getAppUrl, integrationsList, featuredCardIds] + () => + getFilteredCards({ + navigateTo, + getAppUrl, + integrationsList, + featuredCardIds, + onCardClicked, + fleet, + application, + }), + [navigateTo, getAppUrl, integrationsList, featuredCardIds, onCardClicked, fleet, application] ); if (featuredCardIds && featuredCardIds.length > 0) { diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx index 2a6597628a26d..cb7872c9fc7c3 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_context.tsx @@ -19,37 +19,38 @@ export interface OnboardingContextValue { } const OnboardingContext = createContext(null); -export const OnboardingContextProvider: React.FC> = - React.memo(({ children, spaceId }) => { - const { telemetry } = useKibana().services; +export const OnboardingContextProvider: React.FC< + PropsWithChildren<{ spaceId: string; fleet: FleetStart }> +> = React.memo(({ children, spaceId, fleet }) => { + const { telemetry } = useKibana().services; - const value = useMemo( - () => ({ - spaceId, - reportCardOpen: (cardId, { auto = false } = {}) => { - telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepOpen, { - stepId: cardId, - trigger: auto ? 'navigation' : 'click', - }); - }, - reportCardComplete: (cardId, { auto = false } = {}) => { - telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepFinished, { - stepId: cardId, - trigger: auto ? 'auto_check' : 'click', - }); - }, - reportCardLinkClicked: (cardId, linkId: string) => { - telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepLinkClicked, { - originStepId: cardId, - stepLinkId: linkId, - }); - }, - }), - [spaceId, telemetry] - ); + const value = useMemo( + () => ({ + spaceId, + reportCardOpen: (cardId, { auto = false } = {}) => { + telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepOpen, { + stepId: cardId, + trigger: auto ? 'navigation' : 'click', + }); + }, + reportCardComplete: (cardId, { auto = false } = {}) => { + telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepFinished, { + stepId: cardId, + trigger: auto ? 'auto_check' : 'click', + }); + }, + reportCardLinkClicked: (cardId, linkId: string) => { + telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepLinkClicked, { + originStepId: cardId, + stepLinkId: linkId, + }); + }, + }), + [spaceId, telemetry] + ); - return {children}; - }); + return {children}; +}); OnboardingContextProvider.displayName = 'OnboardingContextProvider'; export const useOnboardingContext = () => { diff --git a/x-pack/plugins/security_solution/public/onboarding/index.ts b/x-pack/plugins/security_solution/public/onboarding/index.ts index b20a2777f6ee7..029406bb97a41 100644 --- a/x-pack/plugins/security_solution/public/onboarding/index.ts +++ b/x-pack/plugins/security_solution/public/onboarding/index.ts @@ -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), }; } } diff --git a/x-pack/plugins/security_solution/public/onboarding/routes.tsx b/x-pack/plugins/security_solution/public/onboarding/routes.tsx index b8ac8aba3e90e..e73676db05cec 100644 --- a/x-pack/plugins/security_solution/public/onboarding/routes.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/routes.tsx @@ -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), diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts index 084e068768e8f..7b9a6b49c2748 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/vm_services.ts @@ -57,7 +57,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);