Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Set onboarding page to open "add integration" step when being used as an empty prompt #175296

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
import React, { memo, useMemo } from 'react';
import { useSourcererDataView } from '../../containers/sourcerer';
import { getOnboardingComponent } from './onboarding';
import type { StepId } from './onboarding/types';

export const LandingPageComponent = memo(() => {
const { indicesExist } = useSourcererDataView();
const OnBoarding = useMemo(() => getOnboardingComponent(), []);
return <OnBoarding indicesExist={indicesExist} />;
});
export const LandingPageComponent = memo(
({ defaultExpandedStep }: { defaultExpandedStep?: StepId }) => {
const { indicesExist } = useSourcererDataView();
const OnBoarding = useMemo(() => getOnboardingComponent(), []);
return <OnBoarding indicesExist={indicesExist} defaultExpandedStep={defaultExpandedStep} />;
Comment on lines +16 to +17
Copy link
Contributor

@semd semd Jan 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this getOnboardingComponent function now that we have the component? Couldn't we just return the lazy component itself from the index.tsx and render it directly here? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
);

LandingPageComponent.displayName = 'LandingPageComponent';
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const CardItemComponent: React.FC<{
onStepClicked,
sectionId,
}) => {
const isExpandedCard = expandedCardSteps[cardId].isExpanded;
const isExpandedCard = expandedCardSteps[cardId]?.isExpanded ?? false;

const cardItem = useMemo(() => getCard({ cardId, sectionId }), [cardId, sectionId]);
const expandedSteps = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
setupActiveSections,
updateActiveSections,
isStepActive,
isDefaultFinishedCardStep,
} from './helpers';
import type { ActiveSections, Card, CardId, Section, Step, StepId } from './types';

Expand Down Expand Up @@ -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<StepId>;
const activeSteps = card.steps?.filter((step) =>
isStepActive(step, activeProducts, onboardingSteps)
);
const stepsDone = new Set([
CreateProjectSteps.createFirstProject,
AddIntegrationsSteps.connectToDataSources,
]) as unknown as Set<StepId>;

const timeInMinutes = getCardTimeInMinutes(activeSteps, stepsDone);

Expand All @@ -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<StepId>;

const timeInMinutes = getCardTimeInMinutes(activeSteps, stepsDone);
Expand All @@ -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<StepId>;
const activeSteps = card.steps?.filter((step) =>
isStepActive(step, activeProducts, onboardingSteps)
);
const stepsDone = new Set([
CreateProjectSteps.createFirstProject,
AddIntegrationsSteps.connectToDataSources,
]) as unknown as Set<StepId>;

const stepsLeft = getCardStepsLeft(activeSteps, stepsDone);

Expand All @@ -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<StepId>;

const stepsLeft = getCardStepsLeft(activeSteps, stepsDone);
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand Down Expand Up @@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,20 @@ export const getCardTimeInMinutes = (activeSteps: Step[] | undefined, stepsDone:
export const getCardStepsLeft = (activeSteps: Step[] | undefined, stepsDone: Set<StepId>) =>
Math.max((activeSteps?.length ?? 0) - (stepsDone.size ?? 0), 0);

export const isStepActive = (step: Step, activeProducts: Set<ProductLine>) =>
!step.productLineRequired ||
step.productLineRequired?.some((condition) => activeProducts.has(condition));
export const isStepActive = (
step: Step,
activeProducts: Set<ProductLine>,
onboardingSteps: StepId[]
) =>
onboardingSteps.includes(step.id) &&
(!step.productLineRequired ||
step.productLineRequired?.some((condition) => activeProducts.has(condition)));

export const getActiveSteps = (
steps: Step[] | undefined,
activeProducts: Set<ProductLine>,
onboardingSteps: StepId[]
) =>
steps?.filter((step) => onboardingSteps.includes(step.id) && isStepActive(step, activeProducts));
) => steps?.filter((step) => isStepActive(step, activeProducts, onboardingSteps));

const getfinishedActiveSteps = (
finishedStepIds: StepId[] | undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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' },
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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;

Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,12 @@ const syncExpandedCardStepsFromStorageToURL = (
}
};

export const useTogglePanel = ({
export const useSetupPanel = ({
defaultExpandedStep,
productTypes,
onboardingSteps,
}: {
defaultExpandedStep?: StepId;
productTypes?: SecurityProductTypes;
onboardingSteps: StepId[];
}) => {
Expand Down Expand Up @@ -120,12 +122,12 @@ export const useTogglePanel = ({
);

const expandedCardsInitialStates: ExpandedCardSteps = useMemo(() => {
if (stepIdFromHash) {
syncExpandedCardStepsToStorageFromURL(stepIdFromHash);
if (defaultExpandedStep || stepIdFromHash) {
syncExpandedCardStepsToStorageFromURL(defaultExpandedStep ?? stepIdFromHash);
}

return getAllExpandedCardStepsFromStorage();
}, [getAllExpandedCardStepsFromStorage, stepIdFromHash]);
}, [defaultExpandedStep, getAllExpandedCardStepsFromStorage, stepIdFromHash]);

const onStepClicked: OnStepClicked = useCallback(
({ stepId, cardId, isExpanded }) => {
Expand Down
Loading