From 142e57b8b9e2b79813f91b33f33281e064a8a8c7 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Wed, 28 Sep 2022 17:47:06 +0200 Subject: [PATCH 01/13] [Guided onboarding] Added a EuiTour for guided onboarding in Integrations --- .../guided_onboarding/public/mocks.tsx | 22 +++++++ x-pack/plugins/fleet/kibana.json | 2 +- .../with_guided_onboarding_tour.tsx | 62 +++++++++++++++++++ .../epm/screens/detail/hooks/index.ts | 1 + .../hooks/use_is_guided_onboarding_active.ts | 46 ++++++++++++++ .../sections/epm/screens/detail/index.tsx | 40 +++++++----- .../detail/utils/get_install_route_options.ts | 4 +- .../public/mock/fleet_start_services.tsx | 3 + x-pack/plugins/fleet/public/plugin.ts | 3 + x-pack/plugins/fleet/tsconfig.json | 1 + 10 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 src/plugins/guided_onboarding/public/mocks.tsx create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts diff --git a/src/plugins/guided_onboarding/public/mocks.tsx b/src/plugins/guided_onboarding/public/mocks.tsx new file mode 100644 index 0000000000000..2920bb315ce5b --- /dev/null +++ b/src/plugins/guided_onboarding/public/mocks.tsx @@ -0,0 +1,22 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +jest.mock('./services/api'); +import { ApiService } from './services/api'; + +const mockApi = ApiService as unknown as jest.Mocked; +import { GuidedOnboardingPluginStart } from '.'; + +const startMock: jest.Mocked = { + guidedOnboardingApi: mockApi, +}; + +export const guidedOnboardingMock = { + createSetup: () => {}, + createStart: () => startMock, +}; diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 79d8bbd40644e..c11cd0d9cdaed 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "configPath": ["xpack", "fleet"], - "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager"], + "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager", "guidedOnboarding"], "optionalPlugins": ["features", "cloud", "usageCollection", "home", "globalSearch", "telemetry", "discover", "ingestPipelines"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "cloudChat", "esUiShared", "infra", "kibanaUtils", "usageCollection", "unifiedSearch"] diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx new file mode 100644 index 0000000000000..6fbadfa664069 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx @@ -0,0 +1,62 @@ +/* + * 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, { useEffect, useState } from 'react'; +import type { FunctionComponent } from 'react'; +import { EuiButton, EuiText, EuiTourStep } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +type TourType = 'addIntegrationButton' | 'integrationsList'; +const getTourConfig = (packageKey: string, tourType: TourType) => { + if (packageKey.startsWith('endpoint') && tourType === 'addIntegrationButton') { + return { + title: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointButton.title', { + defaultMessage: 'Add Elastic Defend', + }), + description: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointButton.description', { + defaultMessage: 'In the next steps we’ll help you get setup with some sensible defaults.', + }), + }; + } + return null; +}; +export const WithGuidedOnboardingTour: FunctionComponent<{ + packageKey: string; + isGuidedOnboardingActive: boolean; + tourType: TourType; +}> = ({ packageKey, isGuidedOnboardingActive, tourType, children }) => { + const [isGuidedOnboardingTourOpen, setIsGuidedOnboardingTourOpen] = + useState(isGuidedOnboardingActive); + useEffect(() => { + setIsGuidedOnboardingTourOpen(isGuidedOnboardingActive); + }, [isGuidedOnboardingActive]); + const config = getTourConfig(packageKey, tourType); + + return config ? ( + {config.description}} + isStepOpen={isGuidedOnboardingTourOpen} + maxWidth={350} + onFinish={() => setIsGuidedOnboardingTourOpen(false)} + step={1} + stepsTotal={1} + title={config.title} + anchorPosition="rightUp" + footerAction={ + setIsGuidedOnboardingTourOpen(false)} size="s" color="success"> + {i18n.translate('xpack.fleet.guidedOnboardingTour.nextButtonLabel', { + defaultMessage: 'Next', + })} + + } + > + {children} + + ) : ( + <>{children} + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts index 997d3b0b48a53..74292b5b7616e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts @@ -6,3 +6,4 @@ */ export { useIsFirstTimeAgentUser } from './use_is_first_time_agent_user'; +export { useIsGuidedOnboardingActive } from './use_is_guided_onboarding_active'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts new file mode 100644 index 0000000000000..495c5afa6e248 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts @@ -0,0 +1,46 @@ +/* + * 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 { useEffect, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; + +import { useStartServices } from '../../../../../hooks'; + +const isElasticDefendIntegration = (packageKey: string): boolean => { + // TODO change to a regex + return packageKey.startsWith('endpoint'); +}; + +const isKubernetesIntegration = (packageKey: string): boolean => { + // TODO change to a regex + return packageKey.startsWith('kubernetes'); +}; + +// currently, we're only checking for endpoint and kubernetes integrations +// for security and observability guides +const getGuideStepByIntegration = (packageKey: string) => { + if (isElasticDefendIntegration(packageKey)) { + return { guideID: 'security', stepID: 'add_data' }; + } + if (isKubernetesIntegration(packageKey)) { + return { guideID: 'observability', stepID: 'add_data' }; + } + return { guideID: '', stepID: '' }; +}; +export const useIsGuidedOnboardingActive = (packageKey: string): boolean => { + const [result, setResult] = useState(false); + const { guidedOnboarding } = useStartServices(); + const { guideID, stepID } = getGuideStepByIntegration(packageKey); + const isGuidedOnboardingStepActive = useObservable( + guidedOnboarding.guidedOnboardingApi!.isGuideStepActive$(guideID, stepID) + ); + useEffect(() => { + setResult(!!isGuidedOnboardingStepActive); + }, [isGuidedOnboardingStepActive]); + + return result; +}; 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 d43afbb28835c..80357ec0b9526 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 @@ -47,7 +47,9 @@ import { Error, Loading, HeaderReleaseBadge } from '../../../../components'; import type { WithHeaderLayoutProps } from '../../../../layouts'; import { WithHeaderLayout } from '../../../../layouts'; -import { useIsFirstTimeAgentUser } from './hooks'; +import { WithGuidedOnboardingTour } from './components/with_guided_onboarding_tour'; + +import { useIsFirstTimeAgentUser, useIsGuidedOnboardingActive } from './hooks'; import { getInstallPkgRouteOptions } from './utils'; import { IntegrationAgentPolicyCount, @@ -154,6 +156,7 @@ export function Detail() { const { isFirstTimeAgentUser = false, isLoading: firstTimeUserLoading } = useIsFirstTimeAgentUser(); + const isGuidedOnboardingActive = useIsGuidedOnboardingActive(pkgkey); // Refresh package info when status change const [oldPackageInstallStatus, setOldPackageStatus] = useState(packageInstallStatus); @@ -292,6 +295,7 @@ export function Detail() { isCloud, isExperimentalAddIntegrationPageEnabled, isFirstTimeAgentUser, + isGuidedOnboardingActive, pkgkey, }); @@ -305,6 +309,7 @@ export function Detail() { isCloud, isExperimentalAddIntegrationPageEnabled, isFirstTimeAgentUser, + isGuidedOnboardingActive, pathname, pkgkey, search, @@ -349,19 +354,25 @@ export function Detail() { { isDivider: true }, { content: ( - + + + ), }, ].map((item, index) => ( @@ -385,6 +396,7 @@ export function Detail() { packageInfo, updateAvailable, isInstalled, + isGuidedOnboardingActive, userCanInstallPackages, getHref, pkgkey, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts index 6a8612a44f42f..f4ac18057dbbf 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.ts @@ -29,6 +29,7 @@ interface GetInstallPkgRouteOptionsParams { isCloud: boolean; isExperimentalAddIntegrationPageEnabled: boolean; isFirstTimeAgentUser: boolean; + isGuidedOnboardingActive: boolean; } const isPackageExemptFromStepsLayout = (pkgkey: string) => @@ -45,13 +46,14 @@ export const getInstallPkgRouteOptions = ({ isFirstTimeAgentUser, isCloud, isExperimentalAddIntegrationPageEnabled, + isGuidedOnboardingActive, }: GetInstallPkgRouteOptionsParams): [string, { path: string; state: unknown }] => { const integrationOpts: { integration?: string } = integration ? { integration } : {}; const packageExemptFromStepsLayout = isPackageExemptFromStepsLayout(pkgkey); const useMultiPageLayout = isExperimentalAddIntegrationPageEnabled && isCloud && - isFirstTimeAgentUser && + (isFirstTimeAgentUser || isGuidedOnboardingActive) && !packageExemptFromStepsLayout; const path = pagePathGetters.add_integration_to_policy({ pkgkey, diff --git a/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx b/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx index c5d9b50111569..86816e296dde3 100644 --- a/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx +++ b/x-pack/plugins/fleet/public/mock/fleet_start_services.tsx @@ -13,6 +13,8 @@ import { coreMock } from '@kbn/core/public/mocks'; import type { IStorage } from '@kbn/kibana-utils-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; + import { setHttpClient } from '../hooks/use_request'; import type { FleetAuthz } from '../../common'; @@ -90,6 +92,7 @@ export const createStartServices = (basePath: string = '/mock'): MockedFleetStar }, storage: new Storage(createMockStore()) as jest.Mocked, authz: fleetAuthzMock, + guidedOnboarding: guidedOnboardingMock.createStart(), }; configureStartServices(startServices); diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index b1d845aa9e52f..3590a80037b1f 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -44,6 +44,7 @@ import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import { PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, setupRouteService, appRoutesService } from '../common'; import { calculateAuthz, calculatePackagePrivilegesFromCapabilities } from '../common/authz'; @@ -101,6 +102,7 @@ export interface FleetStartDeps { share: SharePluginStart; cloud?: CloudStart; usageCollection?: UsageCollectionStart; + guidedOnboarding: GuidedOnboardingPluginStart; } export interface FleetStartServices extends CoreStart, Exclude { @@ -110,6 +112,7 @@ export interface FleetStartServices extends CoreStart, Exclude { diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 320843546a305..c9c730b6a170c 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -27,6 +27,7 @@ { "path": "../licensing/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, { "path": "../encrypted_saved_objects/tsconfig.json" }, + {"path": "../../../src/plugins/guided_onboarding/tsconfig.json"}, // optionalPlugins from ./kibana.json { "path": "../security/tsconfig.json" }, From d4569f1dfce7ef0e67edf47488c25fb8a9789018 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Thu, 29 Sep 2022 18:34:00 +0200 Subject: [PATCH 02/13] [Guided onboarding] Added data step completion for Elastic Defend --- .../constants/guides_config/security.ts | 5 +++ .../guided_onboarding/public/services/api.ts | 26 +++++++++++++++ .../public/services/helpers.ts | 22 +++++++++++++ src/plugins/guided_onboarding/public/types.ts | 1 + .../confirm_incoming_data_with_preview.tsx | 11 +++++++ .../hooks/use_is_guided_onboarding_active.ts | 32 +++---------------- .../sections/epm/screens/detail/index.tsx | 2 +- 7 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts index df17d00d7f2d4..769d95f978edc 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts @@ -21,6 +21,11 @@ export const securityConfig: GuideConfig = { 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', ], + integration: 'endpoint', + location: { + appID: 'integrations', + path: '/browse/security', + }, }, { id: 'rules', diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 1adfaa5d8cc23..755eeb6246aad 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -300,6 +300,32 @@ export class ApiService { return undefined; } + + /** + * An observable with the boolean value if the guided onboarding is currently active for the integration. + * Returns true, if the passed integration is used in the current guide's step. + * Returns false otherwise. + * @param {string} guideID the integration ID (package key) to check for in the guided onboarding config + * @return {Observable} an observable with the boolean value + */ + public isGuidedOnboardingActiveForIntegration$(integration?: string): Observable { + return this.fetchGuideState$().pipe( + map((state) => { + return isIntegrationInGuideStep(state, integration); + }) + ); + } + + public async completeGuidedOnboardingForIntegration(integration?: string) { + if (integration) { + const currentState = await firstValueFrom(this.fetchGuideState$()); + const isIntegrationStepActive = isIntegrationInGuideStep(currentState, integration); + if (isIntegrationStepActive) { + return await this.completeGuideStep(currentState.activeGuide, currentState.activeStep); + } + } + return undefined; + } } export const apiService = new ApiService(); diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts index ea4245be99150..a77f0246261fe 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -9,6 +9,7 @@ import type { GuideId } from '../../common/types'; import { guidesConfig } from '../constants/guides_config'; import type { GuideConfig, StepConfig } from '../types'; +import { GuideConfig, GuidedOnboardingState, StepConfig, UseCase } from '../types'; export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { if (guideID && Object.keys(guidesConfig).includes(guideID)) { @@ -33,3 +34,24 @@ export const isLastStep = (guideID: string, stepID: string): boolean => { } return false; }; + +export const getNextStep = (guideID: string, stepID: string): string | undefined => { + const guide = getGuideConfig(guideID); + const activeStepIndex = getStepIndex(guideID, stepID); + if (activeStepIndex > -1 && guide?.steps[activeStepIndex + 1]) { + return guide?.steps[activeStepIndex + 1].id; + } +}; + +const getStepConfig = (guideID: string, stepID: string): StepConfig | undefined => { + const guide = getGuideConfig(guideID); + return guide?.steps.find((step) => step.id === stepID); +}; + +export const isIntegrationInGuideStep = ( + state: GuidedOnboardingState, + integration?: string +): boolean => { + const stepConfig = getStepConfig(state.activeGuide, state.activeStep); + return stepConfig ? stepConfig.integration === integration : false; +}; diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 4a16c16336c6b..41b1f18fe3df1 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -34,6 +34,7 @@ export interface StepConfig { path: string; }; status?: StepStatus; + integration?: string; } export interface GuideConfig { title: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index 011134151cbd7..910e05fccbf26 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -27,6 +27,10 @@ import type { SearchHit } from '@kbn/es-types'; import styled from 'styled-components'; +import { useIsGuidedOnboardingActive } from '../../../../../../integrations/sections/epm/screens/detail/hooks'; + +import { useStartServices } from '../../../../../../../hooks'; + import type { PackageInfo } from '../../../../../../../../common'; import { @@ -136,8 +140,15 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent = ({ ); const { enrolledAgents, numAgentsWithData } = useGetAgentIncomingData(incomingData, packageInfo); + const isGuidedOnboardingActive = useIsGuidedOnboardingActive(packageInfo?.name); + const { guidedOnboarding } = useStartServices(); if (!isLoading && enrolledAgents > 0 && numAgentsWithData > 0) { setAgentDataConfirmed(true); + if (isGuidedOnboardingActive) { + guidedOnboarding.guidedOnboardingApi?.completeGuidedOnboardingForIntegration( + packageInfo?.name + ); + } } if (!agentDataConfirmed) { return ( diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts index 495c5afa6e248..88bcdc49ef8ef 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts @@ -10,37 +10,15 @@ import useObservable from 'react-use/lib/useObservable'; import { useStartServices } from '../../../../../hooks'; -const isElasticDefendIntegration = (packageKey: string): boolean => { - // TODO change to a regex - return packageKey.startsWith('endpoint'); -}; - -const isKubernetesIntegration = (packageKey: string): boolean => { - // TODO change to a regex - return packageKey.startsWith('kubernetes'); -}; - -// currently, we're only checking for endpoint and kubernetes integrations -// for security and observability guides -const getGuideStepByIntegration = (packageKey: string) => { - if (isElasticDefendIntegration(packageKey)) { - return { guideID: 'security', stepID: 'add_data' }; - } - if (isKubernetesIntegration(packageKey)) { - return { guideID: 'observability', stepID: 'add_data' }; - } - return { guideID: '', stepID: '' }; -}; -export const useIsGuidedOnboardingActive = (packageKey: string): boolean => { +export const useIsGuidedOnboardingActive = (packageName?: string): boolean => { const [result, setResult] = useState(false); const { guidedOnboarding } = useStartServices(); - const { guideID, stepID } = getGuideStepByIntegration(packageKey); - const isGuidedOnboardingStepActive = useObservable( - guidedOnboarding.guidedOnboardingApi!.isGuideStepActive$(guideID, stepID) + const isGuidedOnboardingActiveForIntegration = useObservable( + guidedOnboarding.guidedOnboardingApi!.isGuidedOnboardingActiveForIntegration$(packageName) ); useEffect(() => { - setResult(!!isGuidedOnboardingStepActive); - }, [isGuidedOnboardingStepActive]); + setResult(!!isGuidedOnboardingActiveForIntegration); + }, [isGuidedOnboardingActiveForIntegration]); return result; }; 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 80357ec0b9526..e748ef5067d6a 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 @@ -156,7 +156,7 @@ export function Detail() { const { isFirstTimeAgentUser = false, isLoading: firstTimeUserLoading } = useIsFirstTimeAgentUser(); - const isGuidedOnboardingActive = useIsGuidedOnboardingActive(pkgkey); + const isGuidedOnboardingActive = useIsGuidedOnboardingActive(pkgName); // Refresh package info when status change const [oldPackageInstallStatus, setOldPackageStatus] = useState(packageInstallStatus); From ff0036cb0cbde1eb2d4a8bad970f194ac5ede3b7 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 30 Sep 2022 14:28:29 +0200 Subject: [PATCH 03/13] [Guided onboarding] Added tests for api --- .../public/services/api.test.ts | 92 +++++++++++++++++++ .../public/services/helpers.test.ts | 56 +++++++++++ 2 files changed, 148 insertions(+) diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index ffe5596bd7e35..068ab503611b0 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -38,6 +38,15 @@ const mockActiveSearchGuideState: GuideState = { ], }; +const securityGuide = 'security'; +const dataStep = 'add_data'; +const rulesStep = 'rules'; +const unsetGuide = 'unset'; +const unsetStep = 'unset'; + +const endpointIntegration = 'endpoint'; +const kubernetesIntegration = 'kubernetes'; + describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; let apiService: ApiService; @@ -329,4 +338,87 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.put).toHaveBeenCalledTimes(1); }); }); + + describe('isGuidedOnboardingActiveForIntegration$', () => { + it('returns true if the integration is part of the active step', async (done) => { + httpClient.get.mockResolvedValue({ + state: { activeGuide: securityGuide, activeStep: dataStep }, + }); + apiService.setup(httpClient); + subscription = apiService + .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .subscribe((isIntegrationInGuideStep) => { + if (isIntegrationInGuideStep) { + done(); + } + }); + }); + + it('returns false if another integration is part of the active step', async (done) => { + httpClient.get.mockResolvedValue({ + state: { activeGuide: securityGuide, activeStep: dataStep }, + }); + apiService.setup(httpClient); + subscription = apiService + .isGuidedOnboardingActiveForIntegration$(kubernetesIntegration) + .subscribe((isIntegrationInGuideStep) => { + if (!isIntegrationInGuideStep) { + done(); + } + }); + }); + + it('returns false if no guide is active', async (done) => { + httpClient.get.mockResolvedValue({ + state: { activeGuide: unsetGuide, activeStep: unsetStep }, + }); + apiService.setup(httpClient); + subscription = apiService + .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .subscribe((isIntegrationInGuideStep) => { + if (!isIntegrationInGuideStep) { + done(); + } + }); + }); + }); + + describe('completeGuidedOnboardingForIntegration', () => { + it(`completes the step if it's active for the integration`, async () => { + httpClient.get.mockResolvedValue({ + state: { activeGuide: securityGuide, activeStep: dataStep }, + }); + apiService.setup(httpClient); + + await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + expect(httpClient.put).toHaveBeenCalledTimes(1); + // this assertion depends on the guides config + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + activeGuide: securityGuide, + activeStep: rulesStep, + }), + }); + }); + + it(`does nothing if the step has a different integration`, async () => { + httpClient.get.mockResolvedValue({ + state: { activeGuide: securityGuide, activeStep: dataStep }, + }); + apiService.setup(httpClient); + + await apiService.completeGuidedOnboardingForIntegration(kubernetesIntegration); + expect(httpClient.put).not.toHaveBeenCalled(); + }); + + it(`does nothing if no guide is active`, async () => { + httpClient.get.mockResolvedValue({ + state: { activeGuide: unsetGuide, activeStep: unsetStep }, + }); + apiService.setup(httpClient); + + await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + expect(httpClient.put).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts index bc09a9185424c..da341cdb27382 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.test.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -8,10 +8,14 @@ import { guidesConfig } from '../constants/guides_config'; import { isLastStep } from './helpers'; +import { GuidedOnboardingState } from '../types'; +import { getNextStep, isIntegrationInGuideStep, isLastStep } from './helpers'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; const lastStep = guidesConfig[searchGuide].steps[2].id; +const secondStep = guidesConfig[searchGuide].steps[1].id; +const lastStep = guidesConfig[searchGuide].steps[guidesConfig[searchGuide].steps.length - 1].id; describe('GuidedOnboarding ApiService helpers', () => { // this test suite depends on the guides config @@ -26,4 +30,56 @@ describe('GuidedOnboarding ApiService helpers', () => { expect(result).toBe(false); }); }); + + describe('getNextStep', () => { + it('returns id of the next step', () => { + const result = getNextStep(searchGuide, firstStep); + expect(result).toEqual(secondStep); + }); + + it('returns undefined if the params are not part of the config', () => { + const result = getNextStep('some_guide', 'some_step'); + expect(result).toBeUndefined(); + }); + + it(`returns undefined if it's the last step`, () => { + const result = getNextStep(searchGuide, lastStep); + expect(result).toBeUndefined(); + }); + }); + + describe('isIntegrationInGuideStep', () => { + const securityAddDataState: GuidedOnboardingState = { + activeGuide: 'security', + activeStep: 'add_data', + }; + it('return true if the integration is defined in the guide step config', () => { + const result = isIntegrationInGuideStep(securityAddDataState, 'endpoint'); + expect(result).toBe(true); + }); + it('returns false if a different integration is defined in the guide step', () => { + const result = isIntegrationInGuideStep(securityAddDataState, 'kubernetes'); + expect(result).toBe(false); + }); + it('returns false if no integration is defined in the guide step', () => { + const securityRulesState: GuidedOnboardingState = { + activeGuide: 'security', + activeStep: 'rules', + }; + const result = isIntegrationInGuideStep(securityRulesState, 'endpoint'); + expect(result).toBe(false); + }); + it('returns false if no guide is active', () => { + const noGuideState: GuidedOnboardingState = { + activeGuide: 'unset', + activeStep: 'unset', + }; + const result = isIntegrationInGuideStep(noGuideState, 'endpoint'); + expect(result).toBe(false); + }); + it('returns false if no integration passed', () => { + const result = isIntegrationInGuideStep(securityAddDataState); + expect(result).toBe(false); + }); + }); }); From 27521cb5cc31a6e2546a3737dd4d5dc3a812b0a4 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 30 Sep 2022 18:13:28 +0200 Subject: [PATCH 04/13] [Guided onboarding] Fixed jest tests --- .../guided_onboarding/public/mocks.tsx | 19 +++++++++------ .../guided_onboarding/public/services/api.ts | 9 ++++++-- src/plugins/guided_onboarding/public/types.ts | 23 ++++++++++++++++++- .../fleet/.storybook/context/index.tsx | 2 ++ 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/plugins/guided_onboarding/public/mocks.tsx b/src/plugins/guided_onboarding/public/mocks.tsx index 2920bb315ce5b..c8748ed393616 100644 --- a/src/plugins/guided_onboarding/public/mocks.tsx +++ b/src/plugins/guided_onboarding/public/mocks.tsx @@ -6,17 +6,22 @@ * Side Public License, v 1. */ -jest.mock('./services/api'); -import { ApiService } from './services/api'; - -const mockApi = ApiService as unknown as jest.Mocked; +import { BehaviorSubject } from 'rxjs'; import { GuidedOnboardingPluginStart } from '.'; -const startMock: jest.Mocked = { - guidedOnboardingApi: mockApi, +const apiServiceMock: jest.Mocked = { + guidedOnboardingApi: { + isGuidedOnboardingActiveForIntegration$: () => new BehaviorSubject(false), + completeGuidedOnboardingForIntegration: jest.fn(), + isGuideStepActive$: () => new BehaviorSubject(false), + completeGuideStep: jest.fn(), + fetchGuideState$: jest.fn(), + updateGuideState: jest.fn(), + setup: jest.fn(), + }, }; export const guidedOnboardingMock = { createSetup: () => {}, - createStart: () => startMock, + createStart: () => apiServiceMock, }; diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 755eeb6246aad..143fa7c08ab6e 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -9,11 +9,14 @@ import { HttpSetup } from '@kbn/core/public'; import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; +import { API_BASE_PATH } from '../../common'; +import { GuidedOnboardingApi, GuidedOnboardingState, UseCase } from '../types'; +import { getNextStep, isIntegrationInGuideStep, isLastStep } from './helpers'; import { API_BASE_PATH } from '../../common/constants'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types'; import { isLastStep, getGuideConfig } from './helpers'; -export class ApiService { +export class ApiService implements GuidedOnboardingApi { private client: HttpSetup | undefined; private onboardingGuideState$!: BehaviorSubject; public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); @@ -316,7 +319,9 @@ export class ApiService { ); } - public async completeGuidedOnboardingForIntegration(integration?: string) { + public async completeGuidedOnboardingForIntegration( + integration?: string + ): Promise<{ state: GuidedOnboardingState } | undefined> { if (integration) { const currentState = await firstValueFrom(this.fetchGuideState$()); const isIntegrationStepActive = isIntegrationInGuideStep(currentState, integration); diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 41b1f18fe3df1..3d4aee4194617 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -6,15 +6,17 @@ * Side Public License, v 1. */ +import { Observable } from 'rxjs'; import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { GuideId, GuideStepIds, StepStatus } from '../common/types'; import { ApiService } from './services/api'; +import { HttpSetup } from '@kbn/core/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GuidedOnboardingPluginSetup {} export interface GuidedOnboardingPluginStart { - guidedOnboardingApi?: ApiService; + guidedOnboardingApi?: GuidedOnboardingApi; } export interface AppPluginStartDependencies { @@ -24,6 +26,25 @@ export interface AppPluginStartDependencies { export interface ClientConfigType { ui: boolean; } +export interface GuidedOnboardingApi { + setup: (httpClient: HttpSetup) => void; + fetchGuideState$: () => Observable; + updateGuideState: ( + newState: GuidedOnboardingState + ) => Promise<{ state: GuidedOnboardingState } | undefined>; + isGuideStepActive$: (guideID: string, stepID: string) => Observable; + completeGuideStep: ( + guideID: string, + stepID: string + ) => Promise<{ state: GuidedOnboardingState } | undefined>; + isGuidedOnboardingActiveForIntegration$: (integration?: string) => Observable; + completeGuidedOnboardingForIntegration: ( + integration?: string + ) => Promise<{ state: GuidedOnboardingState } | undefined>; +} + +export type UseCase = 'observability' | 'security' | 'search'; +export type StepStatus = 'incomplete' | 'complete' | 'in_progress'; export interface StepConfig { id: GuideStepIds; diff --git a/x-pack/plugins/fleet/.storybook/context/index.tsx b/x-pack/plugins/fleet/.storybook/context/index.tsx index 1d5416cf0483d..411f0030ccb6c 100644 --- a/x-pack/plugins/fleet/.storybook/context/index.tsx +++ b/x-pack/plugins/fleet/.storybook/context/index.tsx @@ -15,6 +15,7 @@ import { I18nProvider } from '@kbn/i18n-react'; import { CoreScopedHistory } from '@kbn/core/public'; import { getStorybookContextProvider } from '@kbn/custom-integrations-plugin/storybook'; +import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; import { IntegrationsAppContext } from '../../public/applications/integrations/app'; import type { FleetConfigType, FleetStartServices } from '../../public/plugin'; @@ -110,6 +111,7 @@ export const StorybookContext: React.FC<{ storyContext?: Parameters writeIntegrationPolicies: true, }, }, + guidedOnboarding: guidedOnboardingMock.createStart(), }), [isCloudEnabled] ); From 696196d1eeedc123350db6739d68d36096c82d04 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 30 Sep 2022 18:21:20 +0200 Subject: [PATCH 05/13] [Guided onboarding] Added a fail safe for guided onboarding plugin not available in fleet --- .../detail/hooks/use_is_guided_onboarding_active.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts index 88bcdc49ef8ef..2c3e66a7ff036 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts @@ -8,13 +8,18 @@ import { useEffect, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; +import { of } from 'rxjs'; + import { useStartServices } from '../../../../../hooks'; export const useIsGuidedOnboardingActive = (packageName?: string): boolean => { const [result, setResult] = useState(false); const { guidedOnboarding } = useStartServices(); const isGuidedOnboardingActiveForIntegration = useObservable( - guidedOnboarding.guidedOnboardingApi!.isGuidedOnboardingActiveForIntegration$(packageName) + // if guided onboarding is not available, return false + guidedOnboarding.guidedOnboardingApi + ? guidedOnboarding.guidedOnboardingApi.isGuidedOnboardingActiveForIntegration$(packageName) + : of(false) ); useEffect(() => { setResult(!!isGuidedOnboardingActiveForIntegration); From e74b69c6431e525a929099177620ba3e2f37645c Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Mon, 3 Oct 2022 10:09:07 +0200 Subject: [PATCH 06/13] [Guided onboarding] Moved the guided onboarding hook to a different folder in the fleet plugin, also fixed tests --- .../components/confirm_incoming_data_with_preview.tsx | 4 +--- .../integrations/sections/epm/screens/detail/hooks/index.ts | 1 - .../integrations/sections/epm/screens/detail/index.tsx | 4 ++-- .../screens/detail/utils/get_install_route_options.test.ts | 5 +++++ x-pack/plugins/fleet/public/hooks/index.ts | 1 + .../detail => }/hooks/use_is_guided_onboarding_active.ts | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) rename x-pack/plugins/fleet/public/{applications/integrations/sections/epm/screens/detail => }/hooks/use_is_guided_onboarding_active.ts (94%) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx index 910e05fccbf26..772d90ada7dc1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/confirm_incoming_data_with_preview.tsx @@ -27,9 +27,7 @@ import type { SearchHit } from '@kbn/es-types'; import styled from 'styled-components'; -import { useIsGuidedOnboardingActive } from '../../../../../../integrations/sections/epm/screens/detail/hooks'; - -import { useStartServices } from '../../../../../../../hooks'; +import { useStartServices, useIsGuidedOnboardingActive } from '../../../../../../../hooks'; import type { PackageInfo } from '../../../../../../../../common'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts index 74292b5b7616e..997d3b0b48a53 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/index.ts @@ -6,4 +6,3 @@ */ export { useIsFirstTimeAgentUser } from './use_is_first_time_agent_user'; -export { useIsGuidedOnboardingActive } from './use_is_guided_onboarding_active'; 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 e748ef5067d6a..cac9f684496e9 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 @@ -39,7 +39,7 @@ import { } from '../../../../hooks'; import { INTEGRATIONS_ROUTING_PATHS } from '../../../../constants'; import { ExperimentalFeaturesService } from '../../../../services'; -import { useGetPackageInfoByKey, useLink, useAgentPolicyContext } from '../../../../hooks'; +import { useGetPackageInfoByKey, useLink, useAgentPolicyContext, useIsGuidedOnboardingActive } from '../../../../hooks'; import { pkgKeyFromPackageInfo } from '../../../../services'; import type { DetailViewPanelName, PackageInfo } from '../../../../types'; import { InstallStatus } from '../../../../types'; @@ -49,7 +49,7 @@ import { WithHeaderLayout } from '../../../../layouts'; import { WithGuidedOnboardingTour } from './components/with_guided_onboarding_tour'; -import { useIsFirstTimeAgentUser, useIsGuidedOnboardingActive } from './hooks'; +import { useIsFirstTimeAgentUser } from './hooks'; import { getInstallPkgRouteOptions } from './utils'; import { IntegrationAgentPolicyCount, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts index e085b9034235b..7d233f0977b8a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/utils/get_install_route_options.test.ts @@ -22,6 +22,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'myintegration-1.0.0', isFirstTimeAgentUser: false, + isGuidedOnboardingActive: false, isCloud: false, isExperimentalAddIntegrationPageEnabled: false, }; @@ -51,6 +52,7 @@ describe('getInstallPkgRouteOptions', () => { pkgkey: 'myintegration-1.0.0', agentPolicyId: '12345', isFirstTimeAgentUser: false, + isGuidedOnboardingActive: false, isCloud: false, isExperimentalAddIntegrationPageEnabled: false, }; @@ -78,6 +80,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'myintegration-1.0.0', isFirstTimeAgentUser: true, + isGuidedOnboardingActive: false, isCloud: true, isExperimentalAddIntegrationPageEnabled: true, }; @@ -105,6 +108,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'apm-1.0.0', isFirstTimeAgentUser: true, + isGuidedOnboardingActive: false, isCloud: true, isExperimentalAddIntegrationPageEnabled: true, }; @@ -137,6 +141,7 @@ describe('getInstallPkgRouteOptions', () => { integration: 'myintegration', pkgkey: 'endpoint-1.0.0', isFirstTimeAgentUser: true, + isGuidedOnboardingActive: false, isCloud: true, isExperimentalAddIntegrationPageEnabled: true, }; diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index 579d1ab5bc3de..79f87a3a77088 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -28,3 +28,4 @@ export * from './use_agent_policy_refresh'; export * from './use_package_installations'; export * from './use_agent_enrollment_flyout_data'; export * from './use_flyout_context'; +export { useIsGuidedOnboardingActive } from './use_is_guided_onboarding_active'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts b/x-pack/plugins/fleet/public/hooks/use_is_guided_onboarding_active.ts similarity index 94% rename from x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts rename to x-pack/plugins/fleet/public/hooks/use_is_guided_onboarding_active.ts index 2c3e66a7ff036..22b8ab9b1a231 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/hooks/use_is_guided_onboarding_active.ts +++ b/x-pack/plugins/fleet/public/hooks/use_is_guided_onboarding_active.ts @@ -10,7 +10,7 @@ import useObservable from 'react-use/lib/useObservable'; import { of } from 'rxjs'; -import { useStartServices } from '../../../../../hooks'; +import { useStartServices } from '.'; export const useIsGuidedOnboardingActive = (packageName?: string): boolean => { const [result, setResult] = useState(false); From df972f80d178910629ad70925474111138d9cd28 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:38:32 +0000 Subject: [PATCH 07/13] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../integrations/sections/epm/screens/detail/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 cac9f684496e9..caefad75ad7a1 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 @@ -39,7 +39,12 @@ import { } from '../../../../hooks'; import { INTEGRATIONS_ROUTING_PATHS } from '../../../../constants'; import { ExperimentalFeaturesService } from '../../../../services'; -import { useGetPackageInfoByKey, useLink, useAgentPolicyContext, useIsGuidedOnboardingActive } from '../../../../hooks'; +import { + useGetPackageInfoByKey, + useLink, + useAgentPolicyContext, + useIsGuidedOnboardingActive, +} from '../../../../hooks'; import { pkgKeyFromPackageInfo } from '../../../../services'; import type { DetailViewPanelName, PackageInfo } from '../../../../types'; import { InstallStatus } from '../../../../types'; From 493462ee2ed08ec83a80811b1a56b60680ed7b80 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:11:45 +0000 Subject: [PATCH 08/13] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- src/plugins/guided_onboarding/public/services/api.ts | 4 ++-- src/plugins/guided_onboarding/public/services/helpers.ts | 2 +- src/plugins/guided_onboarding/public/types.ts | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 143fa7c08ab6e..1fd1a7e742a96 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -10,8 +10,8 @@ import { HttpSetup } from '@kbn/core/public'; import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; import { API_BASE_PATH } from '../../common'; -import { GuidedOnboardingApi, GuidedOnboardingState, UseCase } from '../types'; -import { getNextStep, isIntegrationInGuideStep, isLastStep } from './helpers'; +import { GuidedOnboardingApi, GuidedOnboardingState } from '../types'; +import { isIntegrationInGuideStep, isLastStep } from './helpers'; import { API_BASE_PATH } from '../../common/constants'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types'; import { isLastStep, getGuideConfig } from './helpers'; diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts index a77f0246261fe..ed8975fbba816 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -9,7 +9,7 @@ import type { GuideId } from '../../common/types'; import { guidesConfig } from '../constants/guides_config'; import type { GuideConfig, StepConfig } from '../types'; -import { GuideConfig, GuidedOnboardingState, StepConfig, UseCase } from '../types'; +import { GuideConfig, GuidedOnboardingState, StepConfig } from '../types'; export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { if (guideID && Object.keys(guidesConfig).includes(guideID)) { diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 3d4aee4194617..247e15f236133 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -8,9 +8,8 @@ import { Observable } from 'rxjs'; import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; -import { GuideId, GuideStepIds, StepStatus } from '../common/types'; -import { ApiService } from './services/api'; import { HttpSetup } from '@kbn/core/public'; +import { GuideId, GuideStepIds, StepStatus } from '../common/types'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GuidedOnboardingPluginSetup {} From 795c6ec39934951f0e9637d18d722712bd07d257 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Tue, 4 Oct 2022 20:01:29 +0200 Subject: [PATCH 09/13] [Guided onboarding] Updates after the merge conflicts --- .../guided_onboarding/public/mocks.tsx | 14 ++- .../public/services/api.mocks.ts | 97 ++++++++++++++++ .../public/services/api.test.ts | 109 +++++++----------- .../guided_onboarding/public/services/api.ts | 31 +++-- .../public/services/helpers.test.ts | 50 ++------ .../public/services/helpers.ts | 37 +++--- src/plugins/guided_onboarding/public/types.ts | 33 ++++-- x-pack/plugins/fleet/public/hooks/index.ts | 2 +- 8 files changed, 218 insertions(+), 155 deletions(-) create mode 100644 src/plugins/guided_onboarding/public/services/api.mocks.ts diff --git a/src/plugins/guided_onboarding/public/mocks.tsx b/src/plugins/guided_onboarding/public/mocks.tsx index c8748ed393616..dff111fba67c8 100644 --- a/src/plugins/guided_onboarding/public/mocks.tsx +++ b/src/plugins/guided_onboarding/public/mocks.tsx @@ -11,13 +11,17 @@ import { GuidedOnboardingPluginStart } from '.'; const apiServiceMock: jest.Mocked = { guidedOnboardingApi: { - isGuidedOnboardingActiveForIntegration$: () => new BehaviorSubject(false), - completeGuidedOnboardingForIntegration: jest.fn(), + setup: jest.fn(), + fetchActiveGuideState$: () => new BehaviorSubject(undefined), + fetchAllGuidesState: jest.fn(), + updateGuideState: jest.fn(), + activateGuide: jest.fn(), + completeGuide: jest.fn(), isGuideStepActive$: () => new BehaviorSubject(false), + startGuideStep: jest.fn(), completeGuideStep: jest.fn(), - fetchGuideState$: jest.fn(), - updateGuideState: jest.fn(), - setup: jest.fn(), + isGuidedOnboardingActiveForIntegration$: () => new BehaviorSubject(false), + completeGuidedOnboardingForIntegration: jest.fn(), }, }; diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts new file mode 100644 index 0000000000000..19dd67c7d7b1b --- /dev/null +++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts @@ -0,0 +1,97 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { GuideState } from '../../common/types'; + +export const searchAddDataActiveState: GuideState = { + guideId: 'search', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'active', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], +}; + +export const searchAddDataInProgressState: GuideState = { + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'in_progress', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], + guideId: 'search', +}; + +export const securityAddDataInProgressState: GuideState = { + guideId: 'security', + status: 'in_progress', + isActive: true, + steps: [ + { + id: 'add_data', + status: 'in_progress', + }, + { + id: 'rules', + status: 'inactive', + }, + ], +}; + +export const securityRulesActivesState: GuideState = { + guideId: 'security', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'rules', + status: 'active', + }, + ], +}; + +export const noGuideActiveState: GuideState = { + guideId: 'security', + status: 'in_progress', + isActive: false, + steps: [ + { + id: 'add_data', + status: 'in_progress', + }, + { + id: 'rules', + status: 'inactive', + }, + ], +}; diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 068ab503611b0..fad2a01d79891 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -14,36 +14,15 @@ import { API_BASE_PATH } from '../../common/constants'; import { guidesConfig } from '../constants/guides_config'; import type { GuideState } from '../../common/types'; import { ApiService } from './api'; +import { + noGuideActiveState, + searchAddDataActiveState, + securityAddDataInProgressState, + securityRulesActivesState, +} from './api.mocks'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; - -const mockActiveSearchGuideState: GuideState = { - guideId: searchGuide, - isActive: true, - status: 'in_progress', - steps: [ - { - id: 'add_data', - status: 'active', - }, - { - id: 'browse_docs', - status: 'inactive', - }, - { - id: 'search_experience', - status: 'inactive', - }, - ], -}; - -const securityGuide = 'security'; -const dataStep = 'add_data'; -const rulesStep = 'rules'; -const unsetGuide = 'unset'; -const unsetStep = 'unset'; - const endpointIntegration = 'endpoint'; const kubernetesIntegration = 'kubernetes'; @@ -55,7 +34,7 @@ describe('GuidedOnboarding ApiService', () => { beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); httpClient.get.mockResolvedValue({ - state: { activeGuide: searchGuide, activeStep: firstStep }, + state: [securityAddDataInProgressState], }); apiService = new ApiService(); apiService.setup(httpClient); @@ -81,7 +60,7 @@ describe('GuidedOnboarding ApiService', () => { await apiService.activateGuide(searchGuide); const state = await firstValueFrom(apiService.fetchActiveGuideState$()); - expect(state).toEqual(mockActiveSearchGuideState); + expect(state).toEqual(searchAddDataActiveState); }); }); @@ -96,14 +75,14 @@ describe('GuidedOnboarding ApiService', () => { describe('updateGuideState', () => { it('sends a request to the put API', async () => { const updatedState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', // update the first step status }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }; await apiService.updateGuideState(updatedState, false); @@ -117,14 +96,14 @@ describe('GuidedOnboarding ApiService', () => { describe('isGuideStepActive$', () => { it('returns true if the step has been started', async (done) => { const updatedState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }; await apiService.updateGuideState(updatedState, false); @@ -139,7 +118,7 @@ describe('GuidedOnboarding ApiService', () => { }); it('returns false if the step is not been started', async (done) => { - await apiService.updateGuideState(mockActiveSearchGuideState, false); + await apiService.updateGuideState(searchAddDataActiveState, false); subscription = apiService .isGuideStepActive$(searchGuide, firstStep) .subscribe((isStepActive) => { @@ -179,21 +158,18 @@ describe('GuidedOnboarding ApiService', () => { }); it('reactivates a guide that has already been started', async () => { - await apiService.activateGuide(searchGuide, mockActiveSearchGuideState); + await apiService.activateGuide(searchGuide, searchAddDataActiveState); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - ...mockActiveSearchGuideState, - isActive: true, - }), + body: JSON.stringify(searchAddDataActiveState), }); }); }); describe('completeGuide', () => { const readyToCompleteGuideState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { id: 'add_data', @@ -233,7 +209,7 @@ describe('GuidedOnboarding ApiService', () => { it('returns undefined if the selected guide has uncompleted steps', async () => { const incompleteGuideState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { id: 'add_data', @@ -258,7 +234,7 @@ describe('GuidedOnboarding ApiService', () => { describe('startGuideStep', () => { beforeEach(async () => { - await apiService.updateGuideState(mockActiveSearchGuideState, false); + await apiService.updateGuideState(searchAddDataActiveState, false); }); it('updates the selected step and marks it as in_progress', async () => { @@ -266,16 +242,16 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, isActive: true, status: 'in_progress', steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }), }); @@ -290,14 +266,14 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuideStep', () => { it(`completes the step when it's in progress`, async () => { const updatedState: GuideState = { - ...mockActiveSearchGuideState, + ...searchAddDataActiveState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'in_progress', // Mark a step as in_progress in order to test the "completeGuideStep" behavior }, - mockActiveSearchGuideState.steps[1], - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[1], + searchAddDataActiveState.steps[2], ], }; await apiService.updateGuideState(updatedState, false); @@ -312,14 +288,14 @@ describe('GuidedOnboarding ApiService', () => { ...updatedState, steps: [ { - id: mockActiveSearchGuideState.steps[0].id, + id: searchAddDataActiveState.steps[0].id, status: 'complete', }, { - id: mockActiveSearchGuideState.steps[1].id, + id: searchAddDataActiveState.steps[1].id, status: 'active', }, - mockActiveSearchGuideState.steps[2], + searchAddDataActiveState.steps[2], ], }), }); @@ -331,7 +307,7 @@ describe('GuidedOnboarding ApiService', () => { }); it('does nothing if the step is not in progress', async () => { - await apiService.updateGuideState(mockActiveSearchGuideState, false); + await apiService.updateGuideState(searchAddDataActiveState, false); await apiService.completeGuideStep(searchGuide, firstStep); // Expect only 1 call from updateGuideState() @@ -342,7 +318,7 @@ describe('GuidedOnboarding ApiService', () => { describe('isGuidedOnboardingActiveForIntegration$', () => { it('returns true if the integration is part of the active step', async (done) => { httpClient.get.mockResolvedValue({ - state: { activeGuide: securityGuide, activeStep: dataStep }, + state: [securityAddDataInProgressState], }); apiService.setup(httpClient); subscription = apiService @@ -356,7 +332,7 @@ describe('GuidedOnboarding ApiService', () => { it('returns false if another integration is part of the active step', async (done) => { httpClient.get.mockResolvedValue({ - state: { activeGuide: securityGuide, activeStep: dataStep }, + state: [securityAddDataInProgressState], }); apiService.setup(httpClient); subscription = apiService @@ -370,7 +346,7 @@ describe('GuidedOnboarding ApiService', () => { it('returns false if no guide is active', async (done) => { httpClient.get.mockResolvedValue({ - state: { activeGuide: unsetGuide, activeStep: unsetStep }, + state: [noGuideActiveState], }); apiService.setup(httpClient); subscription = apiService @@ -386,7 +362,7 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuidedOnboardingForIntegration', () => { it(`completes the step if it's active for the integration`, async () => { httpClient.get.mockResolvedValue({ - state: { activeGuide: securityGuide, activeStep: dataStep }, + state: [securityAddDataInProgressState], }); apiService.setup(httpClient); @@ -394,16 +370,13 @@ describe('GuidedOnboarding ApiService', () => { expect(httpClient.put).toHaveBeenCalledTimes(1); // this assertion depends on the guides config expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - activeGuide: securityGuide, - activeStep: rulesStep, - }), + body: JSON.stringify(securityRulesActivesState), }); }); it(`does nothing if the step has a different integration`, async () => { httpClient.get.mockResolvedValue({ - state: { activeGuide: securityGuide, activeStep: dataStep }, + state: [securityAddDataInProgressState], }); apiService.setup(httpClient); @@ -413,7 +386,7 @@ describe('GuidedOnboarding ApiService', () => { it(`does nothing if no guide is active`, async () => { httpClient.get.mockResolvedValue({ - state: { activeGuide: unsetGuide, activeStep: unsetStep }, + state: [noGuideActiveState], }); apiService.setup(httpClient); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 1fd1a7e742a96..c3665281c6575 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -9,12 +9,15 @@ import { HttpSetup } from '@kbn/core/public'; import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; -import { API_BASE_PATH } from '../../common'; -import { GuidedOnboardingApi, GuidedOnboardingState } from '../types'; -import { isIntegrationInGuideStep, isLastStep } from './helpers'; +import { GuidedOnboardingApi } from '../types'; +import { + getGuideConfig, + getInProgressStepId, + isIntegrationInGuideStep, + isLastStep, +} from './helpers'; import { API_BASE_PATH } from '../../common/constants'; import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types'; -import { isLastStep, getGuideConfig } from './helpers'; export class ApiService implements GuidedOnboardingApi { private client: HttpSetup | undefined; @@ -312,24 +315,28 @@ export class ApiService implements GuidedOnboardingApi { * @return {Observable} an observable with the boolean value */ public isGuidedOnboardingActiveForIntegration$(integration?: string): Observable { - return this.fetchGuideState$().pipe( + return this.fetchActiveGuideState$().pipe( map((state) => { - return isIntegrationInGuideStep(state, integration); + return state ? isIntegrationInGuideStep(state, integration) : false; }) ); } public async completeGuidedOnboardingForIntegration( integration?: string - ): Promise<{ state: GuidedOnboardingState } | undefined> { + ): Promise<{ state: GuideState } | undefined> { if (integration) { - const currentState = await firstValueFrom(this.fetchGuideState$()); - const isIntegrationStepActive = isIntegrationInGuideStep(currentState, integration); - if (isIntegrationStepActive) { - return await this.completeGuideStep(currentState.activeGuide, currentState.activeStep); + const currentState = await firstValueFrom(this.fetchActiveGuideState$()); + if (currentState) { + const inProgressStepId = getInProgressStepId(currentState); + if (inProgressStepId) { + const isIntegrationStepActive = isIntegrationInGuideStep(currentState, integration); + if (isIntegrationStepActive) { + return await this.completeGuideStep(currentState?.guideId, inProgressStepId); + } + } } } - return undefined; } } diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts index da341cdb27382..586566fe9488b 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.test.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -7,14 +7,15 @@ */ import { guidesConfig } from '../constants/guides_config'; -import { isLastStep } from './helpers'; -import { GuidedOnboardingState } from '../types'; -import { getNextStep, isIntegrationInGuideStep, isLastStep } from './helpers'; +import { isIntegrationInGuideStep, isLastStep } from './helpers'; +import { + noGuideActiveState, + securityAddDataInProgressState, + securityRulesActivesState, +} from './api.mocks'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; -const lastStep = guidesConfig[searchGuide].steps[2].id; -const secondStep = guidesConfig[searchGuide].steps[1].id; const lastStep = guidesConfig[searchGuide].steps[guidesConfig[searchGuide].steps.length - 1].id; describe('GuidedOnboarding ApiService helpers', () => { @@ -31,54 +32,25 @@ describe('GuidedOnboarding ApiService helpers', () => { }); }); - describe('getNextStep', () => { - it('returns id of the next step', () => { - const result = getNextStep(searchGuide, firstStep); - expect(result).toEqual(secondStep); - }); - - it('returns undefined if the params are not part of the config', () => { - const result = getNextStep('some_guide', 'some_step'); - expect(result).toBeUndefined(); - }); - - it(`returns undefined if it's the last step`, () => { - const result = getNextStep(searchGuide, lastStep); - expect(result).toBeUndefined(); - }); - }); - describe('isIntegrationInGuideStep', () => { - const securityAddDataState: GuidedOnboardingState = { - activeGuide: 'security', - activeStep: 'add_data', - }; it('return true if the integration is defined in the guide step config', () => { - const result = isIntegrationInGuideStep(securityAddDataState, 'endpoint'); + const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'endpoint'); expect(result).toBe(true); }); it('returns false if a different integration is defined in the guide step', () => { - const result = isIntegrationInGuideStep(securityAddDataState, 'kubernetes'); + const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'kubernetes'); expect(result).toBe(false); }); it('returns false if no integration is defined in the guide step', () => { - const securityRulesState: GuidedOnboardingState = { - activeGuide: 'security', - activeStep: 'rules', - }; - const result = isIntegrationInGuideStep(securityRulesState, 'endpoint'); + const result = isIntegrationInGuideStep(securityRulesActivesState, 'endpoint'); expect(result).toBe(false); }); it('returns false if no guide is active', () => { - const noGuideState: GuidedOnboardingState = { - activeGuide: 'unset', - activeStep: 'unset', - }; - const result = isIntegrationInGuideStep(noGuideState, 'endpoint'); + const result = isIntegrationInGuideStep(noGuideActiveState, 'endpoint'); expect(result).toBe(false); }); it('returns false if no integration passed', () => { - const result = isIntegrationInGuideStep(securityAddDataState); + const result = isIntegrationInGuideStep(securityAddDataInProgressState); expect(result).toBe(false); }); }); diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts index ed8975fbba816..0e738646c558d 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -6,10 +6,9 @@ * Side Public License, v 1. */ -import type { GuideId } from '../../common/types'; +import type { GuideId, GuideState, GuideStepIds } from '../../common/types'; import { guidesConfig } from '../constants/guides_config'; -import type { GuideConfig, StepConfig } from '../types'; -import { GuideConfig, GuidedOnboardingState, StepConfig } from '../types'; +import { GuideConfig, StepConfig } from '../types'; export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { if (guideID && Object.keys(guidesConfig).includes(guideID)) { @@ -35,23 +34,25 @@ export const isLastStep = (guideID: string, stepID: string): boolean => { return false; }; -export const getNextStep = (guideID: string, stepID: string): string | undefined => { - const guide = getGuideConfig(guideID); - const activeStepIndex = getStepIndex(guideID, stepID); - if (activeStepIndex > -1 && guide?.steps[activeStepIndex + 1]) { - return guide?.steps[activeStepIndex + 1].id; - } +export const getInProgressStepId = (state: GuideState): GuideStepIds | undefined => { + const inProgressStep = state.steps.find((step) => step.status === 'in_progress'); + return inProgressStep ? inProgressStep.id : undefined; }; -const getStepConfig = (guideID: string, stepID: string): StepConfig | undefined => { - const guide = getGuideConfig(guideID); - return guide?.steps.find((step) => step.id === stepID); +const getInProgressStepConfig = (state: GuideState): StepConfig | undefined => { + const inProgressStepId = getInProgressStepId(state); + if (inProgressStepId) { + const config = getGuideConfig(state.guideId); + if (config) { + return config.steps.find((step) => step.id === inProgressStepId); + } + } }; -export const isIntegrationInGuideStep = ( - state: GuidedOnboardingState, - integration?: string -): boolean => { - const stepConfig = getStepConfig(state.activeGuide, state.activeStep); - return stepConfig ? stepConfig.integration === integration : false; +export const isIntegrationInGuideStep = (state: GuideState, integration?: string): boolean => { + if (state.isActive) { + const stepConfig = getInProgressStepConfig(state); + return stepConfig ? stepConfig.integration === integration : false; + } + return false; }; diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 247e15f236133..dbc300bf0d43d 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -9,7 +9,7 @@ import { Observable } from 'rxjs'; import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { HttpSetup } from '@kbn/core/public'; -import { GuideId, GuideStepIds, StepStatus } from '../common/types'; +import { GuideId, GuideState, GuideStepIds, StepStatus } from '../common/types'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface GuidedOnboardingPluginSetup {} @@ -25,26 +25,35 @@ export interface AppPluginStartDependencies { export interface ClientConfigType { ui: boolean; } + export interface GuidedOnboardingApi { setup: (httpClient: HttpSetup) => void; - fetchGuideState$: () => Observable; + fetchActiveGuideState$: () => Observable; + fetchAllGuidesState: () => Promise<{ state: GuideState[] } | undefined>; updateGuideState: ( - newState: GuidedOnboardingState - ) => Promise<{ state: GuidedOnboardingState } | undefined>; - isGuideStepActive$: (guideID: string, stepID: string) => Observable; + newState: GuideState, + panelState: boolean + ) => Promise<{ state: GuideState } | undefined>; + activateGuide: ( + guideId: GuideId, + guide?: GuideState + ) => Promise<{ state: GuideState } | undefined>; + completeGuide: (guideId: GuideId) => Promise<{ state: GuideState } | undefined>; + isGuideStepActive$: (guideId: GuideId, stepId: GuideStepIds) => Observable; + startGuideStep: ( + guideId: GuideId, + stepId: GuideStepIds + ) => Promise<{ state: GuideState } | undefined>; completeGuideStep: ( - guideID: string, - stepID: string - ) => Promise<{ state: GuidedOnboardingState } | undefined>; + guideId: GuideId, + stepId: GuideStepIds + ) => Promise<{ state: GuideState } | undefined>; isGuidedOnboardingActiveForIntegration$: (integration?: string) => Observable; completeGuidedOnboardingForIntegration: ( integration?: string - ) => Promise<{ state: GuidedOnboardingState } | undefined>; + ) => Promise<{ state: GuideState } | undefined>; } -export type UseCase = 'observability' | 'security' | 'search'; -export type StepStatus = 'incomplete' | 'complete' | 'in_progress'; - export interface StepConfig { id: GuideStepIds; title: string; diff --git a/x-pack/plugins/fleet/public/hooks/index.ts b/x-pack/plugins/fleet/public/hooks/index.ts index 79f87a3a77088..b155ccf63a0db 100644 --- a/x-pack/plugins/fleet/public/hooks/index.ts +++ b/x-pack/plugins/fleet/public/hooks/index.ts @@ -28,4 +28,4 @@ export * from './use_agent_policy_refresh'; export * from './use_package_installations'; export * from './use_agent_enrollment_flyout_data'; export * from './use_flyout_context'; -export { useIsGuidedOnboardingActive } from './use_is_guided_onboarding_active'; +export * from './use_is_guided_onboarding_active'; From 852bd97fd718ec2f3b4fff8c074555d90912c9af Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Tue, 4 Oct 2022 20:06:11 +0200 Subject: [PATCH 10/13] [Guided onboarding] Fixed typos --- src/plugins/guided_onboarding/public/services/api.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index c3665281c6575..9ffe13bbbb63f 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -108,8 +108,8 @@ export class ApiService implements GuidedOnboardingApi { /** * Activates a guide by guideId * This is useful for the onboarding landing page, when a user selects a guide to start or continue - * @param {GuideId} guideID the id of the guide (one of search, observability, security) - * @param {GuideState} guideState (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) + * @param {GuideId} guideId the id of the guide (one of search, observability, security) + * @param {GuideState} guide (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) * @return {Promise} a promise with the updated guide state */ public async activateGuide( @@ -156,7 +156,7 @@ export class ApiService implements GuidedOnboardingApi { * Completes a guide * Updates the overall guide status to 'complete', and marks it as inactive * This is useful for the dropdown panel, when the user clicks the "Continue using Elastic" button after completing all steps - * @param {GuideId} guideID the id of the guide (one of search, observability, security) + * @param {GuideId} guideId the id of the guide (one of search, observability, security) * @return {Promise} a promise with the updated guide state */ public async completeGuide(guideId: GuideId): Promise<{ state: GuideState } | undefined> { @@ -311,7 +311,7 @@ export class ApiService implements GuidedOnboardingApi { * An observable with the boolean value if the guided onboarding is currently active for the integration. * Returns true, if the passed integration is used in the current guide's step. * Returns false otherwise. - * @param {string} guideID the integration ID (package key) to check for in the guided onboarding config + * @param {string} integration the integration (package name) to check for in the guided onboarding config * @return {Observable} an observable with the boolean value */ public isGuidedOnboardingActiveForIntegration$(integration?: string): Observable { From c1fd07ebb7b034b57588f0e6c04466480b828010 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Tue, 4 Oct 2022 21:43:31 +0200 Subject: [PATCH 11/13] [Guided onboarding] Fixed types error --- .../screens/detail/components/with_guided_onboarding_tour.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx index 6fbadfa664069..1b889c8e7d77f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx @@ -6,7 +6,7 @@ */ import React, { useEffect, useState } from 'react'; -import type { FunctionComponent } from 'react'; +import type { FunctionComponent, ReactElement } from 'react'; import { EuiButton, EuiText, EuiTourStep } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -28,6 +28,7 @@ export const WithGuidedOnboardingTour: FunctionComponent<{ packageKey: string; isGuidedOnboardingActive: boolean; tourType: TourType; + children: ReactElement; }> = ({ packageKey, isGuidedOnboardingActive, tourType, children }) => { const [isGuidedOnboardingTourOpen, setIsGuidedOnboardingTourOpen] = useState(isGuidedOnboardingActive); From dd6b92d7840c3f8717e3640ea12eb64182f9a0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 5 Oct 2022 13:13:21 +0200 Subject: [PATCH 12/13] Update x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx Co-authored-by: Kelly Murphy --- .../screens/detail/components/with_guided_onboarding_tour.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx index 1b889c8e7d77f..365bafee92321 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx @@ -18,7 +18,7 @@ const getTourConfig = (packageKey: string, tourType: TourType) => { defaultMessage: 'Add Elastic Defend', }), description: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointButton.description', { - defaultMessage: 'In the next steps we’ll help you get setup with some sensible defaults.', + defaultMessage: 'In just a few steps, configure your data with our recommended defaults. You can change this later.', }), }; } From 01daf38083991971e14b70145b23a6a10b7db0a1 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 5 Oct 2022 11:19:58 +0000 Subject: [PATCH 13/13] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../screens/detail/components/with_guided_onboarding_tour.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx index 365bafee92321..2ea4a6775d6e6 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/with_guided_onboarding_tour.tsx @@ -18,7 +18,8 @@ const getTourConfig = (packageKey: string, tourType: TourType) => { defaultMessage: 'Add Elastic Defend', }), description: i18n.translate('xpack.fleet.guidedOnboardingTour.endpointButton.description', { - defaultMessage: 'In just a few steps, configure your data with our recommended defaults. You can change this later.', + defaultMessage: + 'In just a few steps, configure your data with our recommended defaults. You can change this later.', }), }; }