diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.test.ts index 9fa382567b79e..fb337a1313040 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.test.ts @@ -11,6 +11,7 @@ import { setupActiveSections, updateActiveSections, isStepActive, + isDefaultFinishedCardStep, } from './helpers'; import type { ActiveSections, Card, CardId, Section, Step, StepId } from './types'; @@ -44,14 +45,19 @@ describe('getCardTimeInMinutes', () => { it('should calculate the total time in minutes for a card correctly', () => { const card = { steps: [ - { id: 'step1', timeInMinutes: 30 }, - { id: 'step2', timeInMinutes: 45 }, - { id: 'step3', timeInMinutes: 15 }, + { id: CreateProjectSteps.createFirstProject, timeInMinutes: 30 }, + { id: OverviewSteps.getToKnowElasticSecurity, timeInMinutes: 45 }, + { id: AddIntegrationsSteps.connectToDataSources, timeInMinutes: 15 }, ], } as unknown as Card; const activeProducts = new Set([ProductLine.security, ProductLine.cloud]); - const activeSteps = card.steps?.filter((step) => isStepActive(step, activeProducts)); - const stepsDone = new Set(['step1', 'step3']) as unknown as Set; + const activeSteps = card.steps?.filter((step) => + isStepActive(step, activeProducts, onboardingSteps) + ); + const stepsDone = new Set([ + CreateProjectSteps.createFirstProject, + AddIntegrationsSteps.connectToDataSources, + ]) as unknown as Set; const timeInMinutes = getCardTimeInMinutes(activeSteps, stepsDone); @@ -62,7 +68,9 @@ describe('getCardTimeInMinutes', () => { const card = {} as Card; const activeProducts = new Set([ProductLine.security, ProductLine.cloud]); - const activeSteps = card.steps?.filter((step) => isStepActive(step, activeProducts)); + const activeSteps = card.steps?.filter((step) => + isStepActive(step, activeProducts, onboardingSteps) + ); const stepsDone = new Set(['step1']) as unknown as Set; const timeInMinutes = getCardTimeInMinutes(activeSteps, stepsDone); @@ -73,10 +81,21 @@ describe('getCardTimeInMinutes', () => { describe('getCardStepsLeft', () => { it('should calculate the number of steps left for a card correctly', () => { - const card = { steps: ['step1', 'step2', 'step3'] } as unknown as Card; + const card = { + steps: [ + { id: CreateProjectSteps.createFirstProject }, + { id: OverviewSteps.getToKnowElasticSecurity }, + { id: AddIntegrationsSteps.connectToDataSources }, + ], + } as unknown as Card; const activeProducts = new Set([ProductLine.security, ProductLine.cloud]); - const activeSteps = card.steps?.filter((step) => isStepActive(step, activeProducts)); - const stepsDone = new Set(['step1', 'step3']) as unknown as Set; + const activeSteps = card.steps?.filter((step) => + isStepActive(step, activeProducts, onboardingSteps) + ); + const stepsDone = new Set([ + CreateProjectSteps.createFirstProject, + AddIntegrationsSteps.connectToDataSources, + ]) as unknown as Set; const stepsLeft = getCardStepsLeft(activeSteps, stepsDone); @@ -86,7 +105,9 @@ describe('getCardStepsLeft', () => { it('should return the total number of steps if the card is null or has no steps', () => { const card = {} as Card; const activeProducts = new Set([ProductLine.security, ProductLine.cloud]); - const activeSteps = card.steps?.filter((step) => isStepActive(step, activeProducts)); + const activeSteps = card.steps?.filter((step) => + isStepActive(step, activeProducts, onboardingSteps) + ); const stepsDone = new Set() as unknown as Set; const stepsLeft = getCardStepsLeft(activeSteps, stepsDone); @@ -103,7 +124,7 @@ describe('isStepActive', () => { } as Step; const activeProducts = new Set([ProductLine.cloud]); - const isActive = isStepActive(step, activeProducts); + const isActive = isStepActive(step, activeProducts, onboardingSteps); expect(isActive).toBe(true); }); @@ -114,7 +135,7 @@ describe('isStepActive', () => { } as Step; const activeProducts = new Set([ProductLine.security]); - const isActive = isStepActive(step, activeProducts); + const isActive = isStepActive(step, activeProducts, onboardingSteps); expect(isActive).toBe(true); }); @@ -126,7 +147,24 @@ describe('isStepActive', () => { } as Step; const activeProducts = new Set([ProductLine.security]); - const isActive = isStepActive(step, activeProducts); + const isActive = isStepActive(step, activeProducts, onboardingSteps); + + expect(isActive).toBe(false); + }); + + it('should return false if it is not included in the onboardingSteps', () => { + const step = { + id: CreateProjectSteps.createFirstProject, + }; + const activeProducts = new Set([ProductLine.cloud]); + + const isActive = isStepActive(step, activeProducts, [ + OverviewSteps.getToKnowElasticSecurity, + AddIntegrationsSteps.connectToDataSources, + ViewDashboardSteps.analyzeData, + EnablePrebuiltRulesSteps.enablePrebuiltRules, + ViewAlertsSteps.viewAlerts, + ]); expect(isActive).toBe(false); }); @@ -347,3 +385,38 @@ describe('updateActiveSections', () => { }); }); }); + +describe('isDefaultFinishedCardStep', () => { + it('should return true if the card is a default finished card', () => { + const cardId = QuickStartSectionCardsId.createFirstProject; + const stepId = CreateProjectSteps.createFirstProject; + + const isDefaultFinished = isDefaultFinishedCardStep(cardId, stepId, onboardingSteps); + + expect(isDefaultFinished).toBe(true); + }); + + it('should return false if the card is not included in default finished steps', () => { + const cardId = QuickStartSectionCardsId.createFirstProject; + const stepId = OverviewSteps.getToKnowElasticSecurity; + + const isDefaultFinished = isDefaultFinishedCardStep(cardId, stepId, onboardingSteps); + + expect(isDefaultFinished).toBe(false); + }); + + it('should return false if the step is not included in the onboarding steps', () => { + const cardId = QuickStartSectionCardsId.createFirstProject; + const stepId = CreateProjectSteps.createFirstProject; + + const isDefaultFinished = isDefaultFinishedCardStep(cardId, stepId, [ + OverviewSteps.getToKnowElasticSecurity, + AddIntegrationsSteps.connectToDataSources, + ViewDashboardSteps.analyzeData, + EnablePrebuiltRulesSteps.enablePrebuiltRules, + ViewAlertsSteps.viewAlerts, + ]); + + expect(isDefaultFinished).toBe(false); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts index 6bf7ff9cced91..5a82555b72e76 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/helpers.ts @@ -41,16 +41,20 @@ export const getCardTimeInMinutes = (activeSteps: Step[] | undefined, stepsDone: export const getCardStepsLeft = (activeSteps: Step[] | undefined, stepsDone: Set) => Math.max((activeSteps?.length ?? 0) - (stepsDone.size ?? 0), 0); -export const isStepActive = (step: Step, activeProducts: Set) => - !step.productLineRequired || - step.productLineRequired?.some((condition) => activeProducts.has(condition)); +export const isStepActive = ( + step: Step, + activeProducts: Set, + onboardingSteps: StepId[] +) => + onboardingSteps.includes(step.id) && + (!step.productLineRequired || + step.productLineRequired?.some((condition) => activeProducts.has(condition))); export const getActiveSteps = ( steps: Step[] | undefined, activeProducts: Set, onboardingSteps: StepId[] -) => - steps?.filter((step) => onboardingSteps.includes(step.id) && isStepActive(step, activeProducts)); +) => steps?.filter((step) => isStepActive(step, activeProducts, onboardingSteps)); const getfinishedActiveSteps = ( finishedStepIds: StepId[] | undefined, diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_toggle_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_setup_panel.test.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_toggle_panel.test.tsx rename to x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_setup_panel.test.tsx index 61fac46b03576..0f8f0ade02bdd 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_toggle_panel.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_setup_panel.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import { renderHook, act } from '@testing-library/react-hooks'; -import { useTogglePanel } from './use_toggle_panel'; +import { useSetupPanel } from './use_setup_panel'; import { onboardingStorage } from '../storage'; import type { StepId } from '../types'; @@ -36,7 +36,7 @@ jest.mock('@kbn/security-solution-navigation', () => ({ }, })); -describe('useTogglePanel', () => { +describe('useSetupPanel', () => { const productTypes = [ { product_line: 'security', product_tier: 'essentials' }, { product_line: 'endpoint', product_tier: 'complete' }, @@ -69,7 +69,7 @@ describe('useTogglePanel', () => { test('should initialize state with correct initial values - when no active products from local storage', () => { (onboardingStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([]); - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { state } = result.current; @@ -129,7 +129,7 @@ describe('useTogglePanel', () => { }); test('should initialize state with correct initial values - when all products active', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { state } = result.current; @@ -194,7 +194,7 @@ describe('useTogglePanel', () => { (onboardingStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([ ProductLine.security, ]); - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { state } = result.current; @@ -253,8 +253,44 @@ describe('useTogglePanel', () => { ); }); + test('should initialize state with correct initial values - when onboardingSteps is specified', () => { + (onboardingStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([ + ProductLine.security, + ]); + const { result } = renderHook(() => + useSetupPanel({ + productTypes, + onboardingSteps, + }) + ); + + const { state } = result.current; + + expect(state.onboardingSteps).toEqual(onboardingSteps); + }); + + test('should sync defaultExpandedStep to storage - when defaultExpandedStep is specified', () => { + (onboardingStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([ + ProductLine.security, + ]); + renderHook(() => + useSetupPanel({ + productTypes, + onboardingSteps, + defaultExpandedStep: OverviewSteps.getToKnowElasticSecurity, + }) + ); + expect(onboardingStorage.resetAllExpandedCardStepsToStorage).toHaveBeenCalled(); + + expect(onboardingStorage.addExpandedCardStepToStorage).toHaveBeenCalledWith( + QuickStartSectionCardsId.watchTheOverviewVideo, + OverviewSteps.getToKnowElasticSecurity + ); + expect(onboardingStorage.getAllExpandedCardStepsFromStorage).toHaveBeenCalled(); + }); + test('should reset all the card steps in storage when a step is expanded. (As it allows only one step open at a time)', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { onStepClicked } = result.current; @@ -271,7 +307,7 @@ describe('useTogglePanel', () => { }); test('should add the current step to storage when it is expanded', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { onStepClicked } = result.current; @@ -292,7 +328,7 @@ describe('useTogglePanel', () => { }); test('should remove the current step from storage when it is collapsed', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { onStepClicked } = result.current; @@ -313,7 +349,7 @@ describe('useTogglePanel', () => { }); test('should call addFinishedStepToStorage when toggleTaskCompleteStatus is executed', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { toggleTaskCompleteStatus } = result.current; @@ -333,7 +369,7 @@ describe('useTogglePanel', () => { }); test('should call removeFinishedStepToStorage when toggleTaskCompleteStatus is executed with undo equals to true', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { toggleTaskCompleteStatus } = result.current; @@ -355,7 +391,7 @@ describe('useTogglePanel', () => { }); test('should call toggleActiveProductsInStorage when onProductSwitchChanged is executed', () => { - const { result } = renderHook(() => useTogglePanel({ productTypes, onboardingSteps })); + const { result } = renderHook(() => useSetupPanel({ productTypes, onboardingSteps })); const { onProductSwitchChanged } = result.current; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_toggle_panel.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_setup_panel.tsx similarity index 99% rename from x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_toggle_panel.tsx rename to x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_setup_panel.tsx index 03b9df4867791..43007af2ea0f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_toggle_panel.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/hooks/use_setup_panel.tsx @@ -65,7 +65,7 @@ const syncExpandedCardStepsFromStorageToURL = ( } }; -export const useTogglePanel = ({ +export const useSetupPanel = ({ defaultExpandedStep, productTypes, onboardingSteps, diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx index 1dffbc613c022..d56fdc69402ca 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.tsx @@ -10,7 +10,7 @@ import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { TogglePanel } from './toggle_panel'; -import { useTogglePanel } from './hooks/use_toggle_panel'; +import { useSetupPanel } from './hooks/use_setup_panel'; import { Progress } from './progress_bar'; import { StepContextProvider } from './context/step_context'; import { CONTENT_WIDTH } from './helpers'; @@ -47,7 +47,7 @@ export const OnboardingComponent: React.FC = ({ totalStepsLeft, expandedCardSteps, }, - } = useTogglePanel({ productTypes, onboardingSteps, defaultExpandedStep }); + } = useSetupPanel({ productTypes, onboardingSteps, defaultExpandedStep }); const productTier = productTypes?.find( (product) => product.product_line === ProductLine.security )?.product_tier;