[SecuritySolution] Get Started Page UI update (#160850)

## Summary

1. Add content max-width: 1150px
2. Change wording `runtime` to `realtime`
3. Enable product toggles based on `product type` if no data from local
storage. (Product type `security` displayed as `Analytics` in toggle but
it's product id changed to `security` to aligned with product types'
configs)

**Follow up:** Wait for UX to confirm if `product tiers (essential /
complete)` affects the cards.

<img width="2559" alt="Screenshot 2023-06-29 at 22 06 39"
src="be5831d2-2538-43fd-82c4-30e8321837ed">


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Angela Chuang 2023-07-03 11:12:03 +01:00 committed by GitHub
parent a6a8f5b9ab
commit 61b792f50f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 569 additions and 218 deletions

View file

@ -7,11 +7,18 @@
import { schema, TypeOf } from '@kbn/config-schema';
export enum ProductLine {
security = 'security',
cloud = 'cloud',
endpoint = 'endpoint',
}
export const productLine = schema.oneOf([
schema.literal('security'),
schema.literal('endpoint'),
schema.literal('cloud'),
schema.literal(ProductLine.security),
schema.literal(ProductLine.endpoint),
schema.literal(ProductLine.cloud),
]);
export type SecurityProductLine = TypeOf<typeof productLine>;
export const productTier = schema.oneOf([schema.literal('essentials'), schema.literal('complete')]);

View file

@ -6,6 +6,7 @@
*/
import { getProductAppFeatures } from './pli_features';
import * as pliConfig from './pli_config';
import { ProductLine } from '../config';
describe('getProductAppFeatures', () => {
it('should return the essentials PLIs features', () => {
@ -18,7 +19,7 @@ describe('getProductAppFeatures', () => {
};
const appFeatureKeys = getProductAppFeatures([
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: ProductLine.security, product_tier: 'essentials' },
]);
expect(appFeatureKeys).toEqual(['foo']);
@ -34,7 +35,7 @@ describe('getProductAppFeatures', () => {
};
const appFeatureKeys = getProductAppFeatures([
{ product_line: 'security', product_tier: 'complete' },
{ product_line: ProductLine.security, product_tier: 'complete' },
]);
expect(appFeatureKeys).toEqual(['foo', 'baz']);
@ -58,9 +59,9 @@ describe('getProductAppFeatures', () => {
};
const appFeatureKeys = getProductAppFeatures([
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'complete' },
{ product_line: 'cloud', product_tier: 'essentials' },
{ product_line: ProductLine.security, product_tier: 'essentials' },
{ product_line: ProductLine.endpoint, product_tier: 'complete' },
{ product_line: ProductLine.cloud, product_tier: 'essentials' },
]);
expect(appFeatureKeys).toEqual(['foo', 'bar', 'repeated', 'qux', 'quux', 'corge', 'garply']);

View file

@ -7,6 +7,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { GetStartedComponent } from './get_started';
import { SecurityProductTypes } from '../../../common/config';
jest.mock('./toggle_panel');
jest.mock('./welcome_panel');
@ -20,9 +21,15 @@ jest.mock('@elastic/eui', () => {
};
});
const productTypes = [
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'complete' },
{ product_line: 'cloud', product_tier: 'complete' },
] as SecurityProductTypes;
describe('GetStartedComponent', () => {
it('should render page title, subtitle, and description', () => {
const { getByText } = render(<GetStartedComponent />);
const { getByText } = render(<GetStartedComponent productTypes={productTypes} />);
const pageTitle = getByText('Welcome');
const subtitle = getByText('Lets get started');
@ -35,8 +42,16 @@ describe('GetStartedComponent', () => {
expect(description).toBeInTheDocument();
});
it('should render Product Switch', () => {
const { getByTestId } = render(<GetStartedComponent productTypes={productTypes} />);
const productSwitch = getByTestId('product-switch');
expect(productSwitch).toBeInTheDocument();
});
it('should render WelcomePanel and TogglePanel', () => {
const { getByTestId } = render(<GetStartedComponent />);
const { getByTestId } = render(<GetStartedComponent productTypes={productTypes} />);
const welcomePanel = getByTestId('welcome-panel');
const togglePanel = getByTestId('toggle-panel');

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { EuiTitle, useEuiTheme } from '@elastic/eui';
import { EuiTitle, useEuiTheme, useEuiShadow } from '@elastic/eui';
import React from 'react';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { css } from '@emotion/react';
@ -17,10 +17,22 @@ import {
GET_STARTED_PAGE_SUBTITLE,
GET_STARTED_PAGE_TITLE,
} from './translations';
import { SecurityProductTypes } from '../../../common/config';
import { ProductSwitch } from './product_switch';
import { useTogglePanel } from './use_toggle_panel';
export const GetStartedComponent: React.FC = () => {
const CONTENT_WIDTH = 1150;
export const GetStartedComponent: React.FC<{ productTypes: SecurityProductTypes }> = ({
productTypes,
}) => {
const { euiTheme } = useEuiTheme();
const shadow = useEuiShadow('s');
const {
onProductSwitchChanged,
onStepClicked,
state: { activeProducts, activeCards, finishedSteps },
} = useTogglePanel({ productTypes });
return (
<KibanaPageTemplate
restrictWidth={false}
@ -38,6 +50,7 @@ export const GetStartedComponent: React.FC = () => {
`}
>
<KibanaPageTemplate.Header
restrictWidth={CONTENT_WIDTH}
css={css`
padding: 0 ${euiTheme.base * 2.25}px;
`}
@ -60,13 +73,39 @@ export const GetStartedComponent: React.FC = () => {
>
<WelcomePanel />
</KibanaPageTemplate.Header>
<KibanaPageTemplate.Section
bottomBorder={false}
grow={true}
restrictWidth={CONTENT_WIDTH}
paddingSize="none"
css={css`
${shadow};
z-index: 1;
flex-grow: 0;
padding: 0 ${euiTheme.base * 2.25}px;
`}
>
<ProductSwitch
onProductSwitchChanged={onProductSwitchChanged}
activeProducts={activeProducts}
euiTheme={euiTheme}
/>
</KibanaPageTemplate.Section>
<KibanaPageTemplate.Section
bottomBorder="extended"
grow={true}
restrictWidth={false}
restrictWidth={CONTENT_WIDTH}
paddingSize="none"
css={css`
padding: 0 ${euiTheme.base * 2.25}px;
`}
>
<TogglePanel />
<TogglePanel
finishedSteps={finishedSteps}
activeCards={activeCards}
activeProducts={activeProducts}
onStepClicked={onStepClicked}
/>
</KibanaPageTemplate.Section>
</KibanaPageTemplate>
);

View file

@ -13,18 +13,18 @@ import {
updateCard,
} from './helpers';
import {
ActiveCard,
ActiveCards,
Card,
CardId,
GetMoreFromElasticSecurityCardId,
GetSetUpCardId,
IntroductionSteps,
ProductId,
Section,
SectionId,
StepId,
} from './types';
import * as sectionsConfigs from './sections';
import { ProductLine } from '../../../common/config';
const mockSections = jest.spyOn(sectionsConfigs, 'getSections');
describe('getCardTimeInMinutes', () => {
it('should calculate the total time in minutes for a card correctly', () => {
@ -74,8 +74,8 @@ describe('getCardStepsLeft', () => {
describe('isCardActive', () => {
it('should return true if the card is active based on the active products', () => {
const card = { productTypeRequired: [ProductId.analytics, ProductId.cloud] } as Card;
const activeProducts = new Set([ProductId.analytics]);
const card = { productLineRequired: [ProductLine.security, ProductLine.cloud] } as Card;
const activeProducts = new Set([ProductLine.security]);
const isActive = isCardActive(card, activeProducts);
@ -84,7 +84,7 @@ describe('isCardActive', () => {
it('should return true if the card has no product type requirement', () => {
const card = {} as Card;
const activeProducts = new Set([ProductId.analytics]);
const activeProducts = new Set([ProductLine.security]);
const isActive = isCardActive(card, activeProducts);
@ -92,8 +92,8 @@ describe('isCardActive', () => {
});
it('should return false if the card is not active based on the active products', () => {
const card = { productTypeRequired: [ProductId.analytics, ProductId.cloud] } as Card;
const activeProducts = new Set([ProductId.endpoint]);
const card = { productLineRequired: [ProductLine.security, ProductLine.cloud] } as Card;
const activeProducts = new Set([ProductLine.endpoint]);
const isActive = isCardActive(card, activeProducts);
@ -140,7 +140,7 @@ describe('setupCards', () => {
};
it('should set up active cards based on active products', () => {
const finishedSteps = {} as unknown as Record<CardId, Set<StepId>>;
const activeProducts = new Set([ProductId.cloud]);
const activeProducts = new Set([ProductLine.cloud]);
const activeCards = setupCards(finishedSteps, activeProducts);
@ -148,8 +148,8 @@ describe('setupCards', () => {
...analyticProductActiveCards,
[SectionId.getSetUp]: {
...analyticProductActiveCards[SectionId.getSetUp],
[GetSetUpCardId.protectYourEnvironmentInRuntime]: {
id: GetSetUpCardId.protectYourEnvironmentInRuntime,
[GetSetUpCardId.protectYourEnvironmentInRealtime]: {
id: GetSetUpCardId.protectYourEnvironmentInRealtime,
timeInMins: 0,
stepsLeft: 0,
},
@ -159,7 +159,7 @@ describe('setupCards', () => {
it('should skip inactive cards based on finished steps and active products', () => {
const finishedSteps = {} as Record<CardId, Set<StepId>>;
const activeProducts = new Set([ProductId.analytics]);
const activeProducts = new Set([ProductLine.security]);
const activeCards = setupCards(finishedSteps, activeProducts);
@ -171,7 +171,7 @@ describe('setupCards', () => {
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
} as unknown as Record<CardId, Set<StepId>>;
const activeProducts: Set<ProductId> = new Set();
const activeProducts: Set<ProductLine> = new Set();
const activeCards = setupCards(finishedSteps, activeProducts);
@ -188,7 +188,7 @@ describe('setupCards', () => {
const finishedSteps = {
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
} as unknown as Record<CardId, Set<StepId>>;
const activeProducts = new Set([ProductId.analytics]);
const activeProducts = new Set([ProductLine.security]);
const activeCards = setupCards(finishedSteps, activeProducts);
@ -202,7 +202,7 @@ describe('updateCard', () => {
const finishedSteps = {
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
} as unknown as Record<CardId, Set<StepId>>;
const activeProducts = new Set([ProductId.analytics, ProductId.cloud]);
const activeProducts = new Set([ProductLine.security, ProductLine.cloud]);
const activeCards = {
[SectionId.getSetUp]: {
@ -221,8 +221,8 @@ describe('updateCard', () => {
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.protectYourEnvironmentInRuntime]: {
id: GetSetUpCardId.protectYourEnvironmentInRuntime,
[GetSetUpCardId.protectYourEnvironmentInRealtime]: {
id: GetSetUpCardId.protectYourEnvironmentInRealtime,
timeInMins: 0,
stepsLeft: 0,
},
@ -244,7 +244,7 @@ describe('updateCard', () => {
timeInMins: 0,
},
},
} as Record<SectionId, Record<CardId, ActiveCard>>;
} as ActiveCards;
it('should update the active card based on finished steps and active products', () => {
const sectionId = SectionId.getSetUp;
@ -273,7 +273,7 @@ describe('updateCard', () => {
it('should return null if the card is inactive based on active products', () => {
const sectionId = SectionId.getSetUp;
const cardId = GetSetUpCardId.protectYourEnvironmentInRuntime;
const cardId = GetSetUpCardId.protectYourEnvironmentInRealtime;
const updatedCards = updateCard({
finishedSteps,

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { ProductLine } from '../../../common/config';
import { getSections } from './sections';
import { ActiveCard, Card, CardId, ProductId, SectionId, StepId } from './types';
import { ActiveCard, ActiveCards, Card, CardId, SectionId, StepId } from './types';
export const getCardTimeInMinutes = (card: Card, stepsDone: Set<StepId>) =>
card.steps?.reduce(
@ -18,13 +19,13 @@ export const getCardTimeInMinutes = (card: Card, stepsDone: Set<StepId>) =>
export const getCardStepsLeft = (card: Card, stepsDone: Set<StepId>) =>
(card.steps?.length ?? 0) - (stepsDone.size ?? 0);
export const isCardActive = (card: Card, activeProducts: Set<ProductId>) =>
!card.productTypeRequired ||
card.productTypeRequired?.some((condition) => activeProducts.has(condition));
export const isCardActive = (card: Card, activeProducts: Set<ProductLine>) =>
!card.productLineRequired ||
card.productLineRequired?.some((condition) => activeProducts.has(condition));
export const setupCards = (
finishedSteps: Record<CardId, Set<StepId>>,
activeProducts: Set<ProductId>
activeProducts: Set<ProductLine>
) =>
activeProducts.size > 0
? getSections().reduce((acc, section) => {
@ -46,7 +47,7 @@ export const setupCards = (
acc[section.id] = cardsInSections;
}
return acc;
}, {} as Record<SectionId, Record<CardId, ActiveCard>>)
}, {} as ActiveCards)
: null;
export const updateCard = ({
@ -57,11 +58,11 @@ export const updateCard = ({
cardId,
}: {
finishedSteps: Record<CardId, Set<StepId>>;
activeProducts: Set<ProductId>;
activeCards: Record<SectionId, Record<CardId, ActiveCard>> | null;
activeProducts: Set<ProductLine>;
activeCards: ActiveCards | null;
sectionId: SectionId;
cardId: CardId;
}): Record<SectionId, Record<CardId, ActiveCard>> | null => {
}): ActiveCards | null => {
const sections = getSections();
const section = sections.find(({ id }) => id === sectionId);
const cards = section?.cards;

View file

@ -13,14 +13,16 @@ import type { GetStartedComponent } from './types';
import { GetStarted } from './lazy';
import { KibanaServicesProvider } from '../../services';
import { ServerlessSecurityPluginStartDependencies } from '../../types';
import { SecurityProductTypes } from '../../../common/config';
export const getSecurityGetStartedComponent = (
core: CoreStart,
pluginsStart: ServerlessSecurityPluginStartDependencies
pluginsStart: ServerlessSecurityPluginStartDependencies,
productTypes: SecurityProductTypes
): GetStartedComponent => {
return () => (
<KibanaServicesProvider core={core} pluginsStart={pluginsStart}>
<GetStarted />
<GetStarted productTypes={productTypes} />
</KibanaServicesProvider>
);
};

View file

@ -6,13 +6,14 @@
*/
import React, { lazy, Suspense } from 'react';
import { EuiLoadingLogo } from '@elastic/eui';
import { SecurityProductTypes } from '../../../common/config';
const GetStartedLazy = lazy(() => import('./get_started'));
const centerLogoStyle = { display: 'flex', margin: 'auto' };
export const GetStarted = () => (
export const GetStarted = ({ productTypes }: { productTypes: SecurityProductTypes }) => (
<Suspense fallback={<EuiLoadingLogo logo="logoSecurity" size="xl" style={centerLogoStyle} />}>
<GetStartedLazy />
<GetStartedLazy productTypes={productTypes} />
</Suspense>
);

View file

@ -9,7 +9,7 @@ import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { ProductSwitch } from './product_switch';
import { EuiThemeComputed } from '@elastic/eui';
import { ProductId } from './types';
import { ProductLine } from '../../../common/config';
describe('ProductSwitch', () => {
const onProductSwitchChangedMock = jest.fn();
@ -49,12 +49,12 @@ describe('ProductSwitch', () => {
fireEvent.click(analyticsSwitch);
expect(onProductSwitchChangedMock).toHaveBeenCalledWith(
expect.objectContaining({ id: 'analytics' })
expect.objectContaining({ id: 'security' })
);
});
it('should have checked switches for activeProducts', () => {
const activeProducts = new Set([ProductId.analytics, ProductId.endpoint]);
const activeProducts = new Set([ProductLine.security, ProductLine.endpoint]);
const { getByTestId } = render(
<ProductSwitch
onProductSwitchChanged={onProductSwitchChangedMock}
@ -63,7 +63,7 @@ describe('ProductSwitch', () => {
/>
);
const analyticsSwitch = getByTestId('analytics');
const analyticsSwitch = getByTestId('security');
const cloudSwitch = getByTestId('cloud');
const endpointSwitch = getByTestId('endpoint');

View file

@ -8,30 +8,30 @@
import { EuiPanel, EuiSwitch, EuiText, EuiThemeComputed, EuiTitle } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { useMemo } from 'react';
import { ProductLine } from '../../../common/config';
import * as i18n from './translations';
import { ProductId, Switch } from './types';
import { Switch } from './types';
const switches: Switch[] = [
{
id: ProductId.analytics,
id: ProductLine.security,
label: i18n.ANALYTICS_SWITCH_LABEL,
},
{
id: ProductId.cloud,
id: ProductLine.cloud,
label: i18n.CLOUD_SWITCH_LABEL,
},
{
id: ProductId.endpoint,
id: ProductLine.endpoint,
label: i18n.ENDPOINT_SWITCH_LABEL,
},
];
const ProductSwitchComponent: React.FC<{
onProductSwitchChanged: (item: Switch) => void;
activeProducts: Set<ProductId>;
shadow?: string;
activeProducts: Set<ProductLine>;
euiTheme: EuiThemeComputed;
}> = ({ onProductSwitchChanged, activeProducts, euiTheme, shadow = '' }) => {
}> = ({ onProductSwitchChanged, activeProducts, euiTheme }) => {
const switchNodes = useMemo(
() =>
switches.map((item) => (
@ -58,8 +58,7 @@ const ProductSwitchComponent: React.FC<{
paddingSize="none"
hasShadow={false}
css={css`
padding: ${euiTheme.base * 1.25}px ${euiTheme.base * 2.25}px;
${shadow};
padding: ${euiTheme.base * 1.25}px 0;
`}
borderRadius="none"
>

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { ProductLine } from '../../../common/config';
import {
reducer,
getFinishedStepsInitialStates,
@ -12,12 +13,11 @@ import {
getActiveCardsInitialStates,
} from './reducer';
import {
ActiveCard,
ActiveCards,
CardId,
GetSetUpCardId,
GetStartedPageActions,
IntroductionSteps,
ProductId,
SectionId,
StepId,
ToggleProductAction,
@ -28,25 +28,25 @@ import {
describe('reducer', () => {
it('should toggle section correctly', () => {
const initialState = {
activeProducts: new Set([ProductId.analytics]),
activeProducts: new Set([ProductLine.security]),
finishedSteps: {} as Record<CardId, Set<StepId>>,
activeCards: {} as Record<SectionId, Record<CardId, ActiveCard>> | null,
activeCards: {} as ActiveCards | null,
};
const action: ToggleProductAction = {
type: GetStartedPageActions.ToggleProduct,
payload: { section: ProductId.analytics },
payload: { section: ProductLine.security },
};
const nextState = reducer(initialState, action);
expect(nextState.activeProducts.has(ProductId.analytics)).toBe(false);
expect(nextState.activeProducts.has(ProductLine.security)).toBe(false);
expect(nextState.activeCards).toBeNull();
});
it('should add a finished step correctly', () => {
const initialState = {
activeProducts: new Set([ProductId.analytics]),
activeProducts: new Set([ProductLine.security]),
finishedSteps: {} as Record<CardId, Set<StepId>>,
activeCards: {
getSetUp: {
@ -56,7 +56,7 @@ describe('reducer', () => {
timeInMins: 3,
},
},
} as unknown as Record<SectionId, Record<CardId, ActiveCard>> | null,
} as unknown as ActiveCards | null,
};
const action: AddFinishedStepAction = {
@ -103,17 +103,17 @@ describe('getFinishedStepsInitialStates', () => {
describe('getActiveSectionsInitialStates', () => {
it('should return the initial states of active sections correctly', () => {
const activeProducts = [ProductId.analytics];
const activeProducts = [ProductLine.security];
const initialStates = getActiveSectionsInitialStates({ activeProducts });
expect(initialStates.has(ProductId.analytics)).toBe(true);
expect(initialStates.has(ProductLine.security)).toBe(true);
});
});
describe('getActiveCardsInitialStates', () => {
it('should return the initial states of active cards correctly', () => {
const activeProducts = new Set([ProductId.analytics]);
const activeProducts = new Set([ProductLine.security]);
const finishedSteps = {
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
} as unknown as Record<CardId, Set<StepId>>;

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import { ProductLine } from '../../../common/config';
import { setupCards, updateCard } from './helpers';
import {
CardId,
GetStartedPageActions,
ProductId,
StepId,
ToggleProductAction,
TogglePanelReducer,
@ -80,13 +80,13 @@ export const getFinishedStepsInitialStates = ({
export const getActiveSectionsInitialStates = ({
activeProducts,
}: {
activeProducts: ProductId[];
activeProducts: ProductLine[];
}) => new Set(activeProducts);
export const getActiveCardsInitialStates = ({
activeProducts,
finishedSteps,
}: {
activeProducts: Set<ProductId>;
activeProducts: Set<ProductLine>;
finishedSteps: Record<CardId, Set<StepId>>;
}) => setupCards(finishedSteps, activeProducts);

View file

@ -8,7 +8,6 @@ import React from 'react';
import {
Section,
ProductId,
SectionId,
GetMoreFromElasticSecurityCardId,
GetSetUpCardId,
@ -17,12 +16,7 @@ import {
import * as i18n from './translations';
import respond from './images/respond.svg';
import protect from './images/protect.svg';
export const ActiveConditions = {
analyticsToggled: [ProductId.analytics],
cloudToggled: [ProductId.cloud],
endpointToggled: [ProductId.endpoint],
};
import { ProductLine } from '../../../common/config';
export const introductionSteps = [
{
@ -85,11 +79,8 @@ export const sections: Section[] = [
{
icon: { type: protect, size: 'xl' },
title: i18n.PROTECT_YOUR_ENVIRONMENT_TITLE,
id: GetSetUpCardId.protectYourEnvironmentInRuntime,
productTypeRequired: [
...ActiveConditions.cloudToggled,
...ActiveConditions.endpointToggled,
],
id: GetSetUpCardId.protectYourEnvironmentInRealtime,
productLineRequired: [ProductLine.cloud, ProductLine.endpoint],
},
],
},

View file

@ -5,10 +5,11 @@
* 2.0.
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { render } from '@testing-library/react';
import { TogglePanel } from './toggle_panel';
import { getStartedStorage as mockGetStartedStorage } from '../../lib/get_started/storage';
import { useSetUpCardSections } from './use_setup_cards';
import { ActiveCards, CardId, GetSetUpCardId, IntroductionSteps, SectionId, StepId } from './types';
import { ProductLine } from '../../../common/config';
jest.mock('@elastic/eui', () => ({
...jest.requireActual('@elastic/eui'),
@ -30,23 +31,57 @@ jest.mock('./use_setup_cards', () => ({
useSetUpCardSections: jest.fn(),
}));
describe('TogglePanel', () => {
const mockUseSetUpCardSections = { setUpSections: jest.fn(() => null) };
const finishedSteps = {
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
} as unknown as Record<CardId, Set<StepId>>;
const activeProducts = new Set([ProductLine.security, ProductLine.cloud]);
const activeCards = {
[SectionId.getSetUp]: {
[GetSetUpCardId.introduction]: {
id: GetSetUpCardId.introduction,
timeInMins: 3,
stepsLeft: 1,
},
[GetSetUpCardId.bringInYourData]: {
id: GetSetUpCardId.bringInYourData,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.activateAndCreateRules]: {
id: GetSetUpCardId.activateAndCreateRules,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.protectYourEnvironmentInRealtime]: {
id: GetSetUpCardId.protectYourEnvironmentInRealtime,
timeInMins: 0,
stepsLeft: 0,
},
},
} as ActiveCards;
describe('TogglePanel', () => {
const mockUseSetUpCardSections = {
setUpSections: jest.fn(() => <div data-test-subj="mock-sections" />),
};
const onStepClicked = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
(useSetUpCardSections as jest.Mock).mockReturnValue(mockUseSetUpCardSections);
});
it('should render the product switch ', () => {
const { getByTestId } = render(<TogglePanel />);
expect(getByTestId('product-switch')).toBeInTheDocument();
});
it('should render empty prompt', () => {
const { getByText } = render(<TogglePanel />);
const { getByText } = render(
<TogglePanel
activeProducts={new Set()}
finishedSteps={finishedSteps}
activeCards={activeCards}
onStepClicked={onStepClicked}
/>
);
expect(getByText(`Hmm, there doesn't seem to be anything there`)).toBeInTheDocument();
expect(
@ -54,16 +89,16 @@ describe('TogglePanel', () => {
).toBeInTheDocument();
});
it('should toggle active sections when a product switch is changed', () => {
const { getByText } = render(<TogglePanel />);
it('should render sections', () => {
const { getByTestId } = render(
<TogglePanel
activeProducts={activeProducts}
finishedSteps={finishedSteps}
activeCards={activeCards}
onStepClicked={onStepClicked}
/>
);
const analyticsSwitch = getByText('Analytics');
const cloudSwitch = getByText('Cloud');
fireEvent.click(analyticsSwitch);
expect(mockGetStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledWith('analytics');
fireEvent.click(cloudSwitch);
expect(mockGetStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledWith('cloud');
expect(getByTestId(`mock-sections`)).toBeInTheDocument();
});
});

View file

@ -5,98 +5,46 @@
* 2.0.
*/
import React, { useCallback, useMemo, useReducer } from 'react';
import React from 'react';
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, useEuiShadow, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { Switch, GetStartedPageActions, StepId, CardId, SectionId } from './types';
import * as i18n from './translations';
import { ProductSwitch } from './product_switch';
import { useSetUpCardSections } from './use_setup_cards';
import { getStartedStorage } from '../../lib/get_started/storage';
import {
getActiveCardsInitialStates,
getActiveSectionsInitialStates,
getFinishedStepsInitialStates,
reducer,
} from './reducer';
const TogglePanelComponent = () => {
import { ActiveCards, CardId, IntroductionSteps, SectionId } from './types';
import { ProductLine } from '../../../common/config';
const TogglePanelComponent: React.FC<{
finishedSteps: Record<CardId, Set<IntroductionSteps>>;
activeCards: ActiveCards | null;
activeProducts: Set<ProductLine>;
onStepClicked: ({
stepId,
cardId,
sectionId,
}: {
stepId: IntroductionSteps;
cardId: CardId;
sectionId: SectionId;
}) => void;
}> = ({ finishedSteps, activeCards, activeProducts, onStepClicked }) => {
const { euiTheme } = useEuiTheme();
const shadow = useEuiShadow('s');
const {
getAllFinishedStepsFromStorage,
getActiveProductsFromStorage,
toggleActiveProductsInStorage,
addFinishedStepToStorage,
} = getStartedStorage;
const finishedStepsInitialStates = useMemo(
() => getFinishedStepsInitialStates({ finishedSteps: getAllFinishedStepsFromStorage() }),
[getAllFinishedStepsFromStorage]
);
const activeSectionsInitialStates = useMemo(
() => getActiveSectionsInitialStates({ activeProducts: getActiveProductsFromStorage() }),
[getActiveProductsFromStorage]
);
const activeCardsInitialStates = useMemo(
() =>
getActiveCardsInitialStates({
activeProducts: activeSectionsInitialStates,
finishedSteps: finishedStepsInitialStates,
}),
[activeSectionsInitialStates, finishedStepsInitialStates]
);
const [state, dispatch] = useReducer(reducer, {
activeProducts: activeSectionsInitialStates,
finishedSteps: finishedStepsInitialStates,
activeCards: activeCardsInitialStates,
});
const { setUpSections } = useSetUpCardSections({ euiTheme, shadow });
const onStepClicked = useCallback(
({ stepId, cardId, sectionId }: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => {
dispatch({
type: GetStartedPageActions.AddFinishedStep,
payload: { stepId, cardId, sectionId },
});
addFinishedStepToStorage(cardId, stepId);
},
[addFinishedStepToStorage]
);
const sectionNodes = setUpSections({
onStepClicked,
finishedSteps: state.finishedSteps,
activeCards: state.activeCards,
finishedSteps,
activeCards,
});
const onProductSwitchChanged = useCallback(
(section: Switch) => {
dispatch({ type: GetStartedPageActions.ToggleProduct, payload: { section: section.id } });
toggleActiveProductsInStorage(section.id);
},
[toggleActiveProductsInStorage]
);
return (
<EuiFlexGroup gutterSize="none" direction="column">
<EuiFlexItem grow={false}>
<ProductSwitch
onProductSwitchChanged={onProductSwitchChanged}
activeProducts={state.activeProducts}
euiTheme={euiTheme}
shadow={shadow}
/>
</EuiFlexItem>
<EuiFlexItem
css={css`
padding: ${euiTheme.size.xs} ${euiTheme.base * 2.25}px;
`}
grow={1}
>
{state.activeProducts.size > 0 ? (
<EuiFlexItem grow={1}>
{activeProducts.size > 0 ? (
sectionNodes
) : (
<EuiEmptyPrompt

View file

@ -157,9 +157,9 @@ export const ACTIVATE_AND_CREATE_RULES_TITLE = i18n.translate(
);
export const PROTECT_YOUR_ENVIRONMENT_TITLE = i18n.translate(
'xpack.serverlessSecurity.getStarted.togglePanel.protectYourEnvironmentInRuntime.title',
'xpack.serverlessSecurity.getStarted.togglePanel.protectYourEnvironmentInRealtime.title',
{
defaultMessage: 'Protect your environment in runtime',
defaultMessage: 'Protect your environment in realtime',
}
);

View file

@ -7,6 +7,7 @@
import { EuiIconProps } from '@elastic/eui';
import React from 'react';
import { ProductLine } from '../../../common/config';
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type GetStartedComponentProps = {};
@ -46,7 +47,7 @@ export interface Step {
export type CardId = GetSetUpCardId | GetMoreFromElasticSecurityCardId;
export interface Card {
productTypeRequired?: ProductId[];
productLineRequired?: ProductLine[];
description?: string | React.ReactNode;
icon?: EuiIconProps;
id: CardId;
@ -56,11 +57,7 @@ export interface Card {
stepsLeft?: number;
}
export enum ProductId {
analytics = 'analytics',
cloud = 'cloud',
endpoint = 'endpoint',
}
export type ActiveCards = Record<SectionId, Record<CardId, ActiveCard>>;
export enum SectionId {
getSetUp = 'getSetUp',
@ -71,7 +68,7 @@ export enum GetSetUpCardId {
activateAndCreateRules = 'activateAndCreateRules',
bringInYourData = 'bringInYourData',
introduction = 'introduction',
protectYourEnvironmentInRuntime = 'protectYourEnvironmentInRuntime',
protectYourEnvironmentInRealtime = 'protectYourEnvironmentInRealtime',
}
export enum IntroductionSteps {
@ -90,14 +87,14 @@ export interface ActiveCard {
stepsLeft: number;
}
export interface TogglePanelReducer {
activeProducts: Set<ProductId>;
activeProducts: Set<ProductLine>;
finishedSteps: Record<CardId, Set<StepId>>;
activeCards: Record<SectionId, Record<CardId, ActiveCard>> | null;
}
export interface ToggleProductAction {
type: GetStartedPageActions.ToggleProduct;
payload: { section: ProductId };
payload: { section: ProductLine };
}
export interface AddFinishedStepAction {
@ -106,7 +103,7 @@ export interface AddFinishedStepAction {
}
export interface Switch {
id: ProductId;
id: ProductLine;
label: string;
}

View file

@ -9,7 +9,7 @@ import { renderHook } from '@testing-library/react-hooks';
import { EuiThemeComputed } from '@elastic/eui';
import { useSetUpCardSections } from './use_setup_cards';
import {
ActiveCard,
ActiveCards,
CardId,
GetMoreFromElasticSecurityCardId,
GetSetUpCardId,
@ -44,7 +44,7 @@ describe('useSetUpCardSections', () => {
id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow,
},
},
} as Record<SectionId, Record<CardId, ActiveCard>>;
} as ActiveCards;
const sections = result.current.setUpSections({
activeCards,

View file

@ -9,7 +9,7 @@ import { EuiSpacer, EuiThemeComputed } from '@elastic/eui';
import React, { useCallback } from 'react';
import { css } from '@emotion/react';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui';
import { ActiveCard, CardId, SectionId, StepId } from './types';
import { ActiveCards, CardId, SectionId, StepId } from './types';
import { CardItem } from './card_item';
import { getSections } from './sections';
@ -30,7 +30,7 @@ export const useSetUpCardSections = ({
}: {
onStepClicked: (params: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => void;
finishedSteps: Record<CardId, Set<StepId>>;
activeCards: Record<SectionId, Record<CardId, ActiveCard>> | null;
activeCards: ActiveCards | null;
sectionId: SectionId;
}) => {
const section = activeCards?.[sectionId];
@ -63,7 +63,7 @@ export const useSetUpCardSections = ({
}: {
onStepClicked: (params: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => void;
finishedSteps: Record<CardId, Set<StepId>>;
activeCards: Record<SectionId, Record<CardId, ActiveCard>> | null;
activeCards: ActiveCards | null;
}) =>
getSections().reduce<React.ReactNode[]>((acc, currentSection) => {
const cardNodes = setUpCards({

View file

@ -0,0 +1,239 @@
/*
* 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 { renderHook, act } from '@testing-library/react-hooks';
import { useTogglePanel } from './use_toggle_panel';
import { getStartedStorage } from '../../lib/get_started/storage';
import { ProductLine, SecurityProductTypes } from '../../../common/config';
import {
GetMoreFromElasticSecurityCardId,
GetSetUpCardId,
IntroductionSteps,
SectionId,
} from './types';
jest.mock('../../lib/get_started/storage');
describe('useTogglePanel', () => {
const productTypes = [
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'complete' },
] as SecurityProductTypes;
beforeEach(() => {
jest.clearAllMocks();
(getStartedStorage.getAllFinishedStepsFromStorage as jest.Mock).mockReturnValue({
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
});
(getStartedStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([
ProductLine.security,
ProductLine.cloud,
ProductLine.endpoint,
]);
});
test('should initialize state with correct initial values - when no active products from local storage', () => {
(getStartedStorage.getAllFinishedStepsFromStorage as jest.Mock).mockReturnValue({});
(getStartedStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([]);
const { result } = renderHook(() => useTogglePanel({ productTypes }));
const { state } = result.current;
expect(state.activeProducts).toEqual(new Set([ProductLine.security, ProductLine.endpoint]));
expect(state.finishedSteps).toEqual({});
expect(state.activeCards).toEqual(
expect.objectContaining({
[SectionId.getSetUp]: {
[GetSetUpCardId.introduction]: {
id: GetSetUpCardId.introduction,
timeInMins: 3,
stepsLeft: 1,
},
[GetSetUpCardId.activateAndCreateRules]: {
id: GetSetUpCardId.activateAndCreateRules,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.bringInYourData]: {
id: GetSetUpCardId.bringInYourData,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.protectYourEnvironmentInRealtime]: {
id: GetSetUpCardId.protectYourEnvironmentInRealtime,
timeInMins: 0,
stepsLeft: 0,
},
},
[SectionId.getMoreFromElasticSecurity]: {
[GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow]: {
id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow,
timeInMins: 0,
stepsLeft: 0,
},
[GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace]: {
id: GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace,
timeInMins: 0,
stepsLeft: 0,
},
[GetMoreFromElasticSecurityCardId.respondToThreats]: {
id: GetMoreFromElasticSecurityCardId.respondToThreats,
timeInMins: 0,
stepsLeft: 0,
},
},
})
);
});
test('should initialize state with correct initial values - when all products active', () => {
const { result } = renderHook(() => useTogglePanel({ productTypes }));
const { state } = result.current;
expect(state.activeProducts).toEqual(
new Set([ProductLine.security, ProductLine.cloud, ProductLine.endpoint])
);
expect(state.finishedSteps).toEqual({
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
});
expect(state.activeCards).toEqual(
expect.objectContaining({
[SectionId.getSetUp]: {
[GetSetUpCardId.introduction]: {
id: GetSetUpCardId.introduction,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.activateAndCreateRules]: {
id: GetSetUpCardId.activateAndCreateRules,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.bringInYourData]: {
id: GetSetUpCardId.bringInYourData,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.protectYourEnvironmentInRealtime]: {
id: GetSetUpCardId.protectYourEnvironmentInRealtime,
timeInMins: 0,
stepsLeft: 0,
},
},
[SectionId.getMoreFromElasticSecurity]: {
[GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow]: {
id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow,
timeInMins: 0,
stepsLeft: 0,
},
[GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace]: {
id: GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace,
timeInMins: 0,
stepsLeft: 0,
},
[GetMoreFromElasticSecurityCardId.respondToThreats]: {
id: GetMoreFromElasticSecurityCardId.respondToThreats,
timeInMins: 0,
stepsLeft: 0,
},
},
})
);
});
test('should initialize state with correct initial values - when only security product active', () => {
(getStartedStorage.getActiveProductsFromStorage as jest.Mock).mockReturnValue([
ProductLine.security,
]);
const { result } = renderHook(() => useTogglePanel({ productTypes }));
const { state } = result.current;
expect(state.activeProducts).toEqual(new Set([ProductLine.security]));
expect(state.finishedSteps).toEqual({
[GetSetUpCardId.introduction]: new Set([IntroductionSteps.watchOverviewVideo]),
});
expect(state.activeCards).toEqual(
expect.objectContaining({
[SectionId.getSetUp]: {
[GetSetUpCardId.introduction]: {
id: GetSetUpCardId.introduction,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.activateAndCreateRules]: {
id: GetSetUpCardId.activateAndCreateRules,
timeInMins: 0,
stepsLeft: 0,
},
[GetSetUpCardId.bringInYourData]: {
id: GetSetUpCardId.bringInYourData,
timeInMins: 0,
stepsLeft: 0,
},
},
[SectionId.getMoreFromElasticSecurity]: {
[GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow]: {
id: GetMoreFromElasticSecurityCardId.masterTheInvestigationsWorkflow,
timeInMins: 0,
stepsLeft: 0,
},
[GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace]: {
id: GetMoreFromElasticSecurityCardId.optimizeYourWorkSpace,
timeInMins: 0,
stepsLeft: 0,
},
[GetMoreFromElasticSecurityCardId.respondToThreats]: {
id: GetMoreFromElasticSecurityCardId.respondToThreats,
timeInMins: 0,
stepsLeft: 0,
},
},
})
);
});
test('should call addFinishedStepToStorage', () => {
const { result } = renderHook(() => useTogglePanel({ productTypes }));
const { onStepClicked } = result.current;
act(() => {
onStepClicked({
stepId: IntroductionSteps.watchOverviewVideo,
cardId: GetSetUpCardId.introduction,
sectionId: SectionId.getSetUp,
});
});
expect(getStartedStorage.addFinishedStepToStorage).toHaveBeenCalledTimes(1);
expect(getStartedStorage.addFinishedStepToStorage).toHaveBeenCalledWith(
GetSetUpCardId.introduction,
IntroductionSteps.watchOverviewVideo
);
});
test('should call toggleActiveProductsInStorage', () => {
const { result } = renderHook(() => useTogglePanel({ productTypes }));
const { onProductSwitchChanged } = result.current;
act(() => {
onProductSwitchChanged({ id: ProductLine.security, label: 'Analytics' });
});
expect(getStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledTimes(1);
expect(getStartedStorage.toggleActiveProductsInStorage).toHaveBeenCalledWith(
ProductLine.security
);
});
});

View file

@ -0,0 +1,77 @@
/*
* 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 { useCallback, useMemo, useReducer } from 'react';
import { ProductLine, SecurityProductTypes } from '../../../common/config';
import { getStartedStorage } from '../../lib/get_started/storage';
import {
getActiveCardsInitialStates,
getActiveSectionsInitialStates,
getFinishedStepsInitialStates,
reducer,
} from './reducer';
import { CardId, GetStartedPageActions, SectionId, StepId, Switch } from './types';
export const useTogglePanel = ({ productTypes }: { productTypes: SecurityProductTypes }) => {
const {
getAllFinishedStepsFromStorage,
getActiveProductsFromStorage,
toggleActiveProductsInStorage,
addFinishedStepToStorage,
} = getStartedStorage;
const finishedStepsInitialStates = useMemo(
() => getFinishedStepsInitialStates({ finishedSteps: getAllFinishedStepsFromStorage() }),
[getAllFinishedStepsFromStorage]
);
const activeSectionsInitialStates = useMemo(() => {
const activeProductsFromStorage = getActiveSectionsInitialStates({
activeProducts: getActiveProductsFromStorage(),
});
return activeProductsFromStorage.size > 0
? activeProductsFromStorage
: new Set(productTypes.map(({ product_line: productLine }) => ProductLine[productLine])) ??
new Set([ProductLine.security, ProductLine.endpoint, ProductLine.cloud]);
}, [getActiveProductsFromStorage, productTypes]);
const activeCardsInitialStates = useMemo(
() =>
getActiveCardsInitialStates({
activeProducts: activeSectionsInitialStates,
finishedSteps: finishedStepsInitialStates,
}),
[activeSectionsInitialStates, finishedStepsInitialStates]
);
const [state, dispatch] = useReducer(reducer, {
activeProducts: activeSectionsInitialStates,
finishedSteps: finishedStepsInitialStates,
activeCards: activeCardsInitialStates,
});
const onStepClicked = useCallback(
({ stepId, cardId, sectionId }: { stepId: StepId; cardId: CardId; sectionId: SectionId }) => {
dispatch({
type: GetStartedPageActions.AddFinishedStep,
payload: { stepId, cardId, sectionId },
});
addFinishedStepToStorage(cardId, stepId);
},
[addFinishedStepToStorage]
);
const onProductSwitchChanged = useCallback(
(section: Switch) => {
dispatch({ type: GetStartedPageActions.ToggleProduct, payload: { section: section.id } });
toggleActiveProductsInStorage(section.id);
},
[toggleActiveProductsInStorage]
);
return { state, onStepClicked, onProductSwitchChanged };
};

View file

@ -8,7 +8,7 @@
import { UpsellingService } from '@kbn/security-solution-plugin/public';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-plugin/common';
import { registerUpsellings, upsellingPages, upsellingSections } from './register_upsellings';
import type { SecurityProductTypes } from '../../../common/config';
import { ProductLine, SecurityProductTypes } from '../../../common/config';
const mockGetProductAppFeatures = jest.fn();
jest.mock('../../../common/pli/pli_features', () => ({
@ -16,9 +16,9 @@ jest.mock('../../../common/pli/pli_features', () => ({
}));
const allProductTypes: SecurityProductTypes = [
{ product_line: 'security', product_tier: 'complete' },
{ product_line: 'endpoint', product_tier: 'complete' },
{ product_line: 'cloud', product_tier: 'complete' },
{ product_line: ProductLine.security, product_tier: 'complete' },
{ product_line: ProductLine.endpoint, product_tier: 'complete' },
{ product_line: ProductLine.cloud, product_tier: 'complete' },
];
describe('registerUpsellings', () => {

View file

@ -6,14 +6,10 @@
*/
import { getStartedStorage } from './storage';
import {
GetSetUpCardId,
IntroductionSteps,
ProductId,
StepId,
} from '../../components/get_started/types';
import { GetSetUpCardId, IntroductionSteps, StepId } from '../../components/get_started/types';
import { storage } from '../storage';
import { MockStorage } from '../__mocks__/storage';
import { ProductLine } from '../../../common/config';
jest.mock('../storage');
@ -33,13 +29,13 @@ describe('useStorage', () => {
});
it('should toggle active products in storage', () => {
expect(getStartedStorage.toggleActiveProductsInStorage(ProductId.analytics)).toEqual([
ProductId.analytics,
expect(getStartedStorage.toggleActiveProductsInStorage(ProductLine.security)).toEqual([
ProductLine.security,
]);
expect(mockStorage.set).toHaveBeenCalledWith('ACTIVE_PRODUCTS', [ProductId.analytics]);
expect(mockStorage.set).toHaveBeenCalledWith('ACTIVE_PRODUCTS', [ProductLine.security]);
mockStorage.set('ACTIVE_PRODUCTS', [ProductId.analytics]);
expect(getStartedStorage.toggleActiveProductsInStorage(ProductId.analytics)).toEqual([]);
mockStorage.set('ACTIVE_PRODUCTS', [ProductLine.security]);
expect(getStartedStorage.toggleActiveProductsInStorage(ProductLine.security)).toEqual([]);
expect(mockStorage.set).toHaveBeenCalledWith('ACTIVE_PRODUCTS', []);
});

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { CardId, ProductId, StepId } from '../../components/get_started/types';
import { ProductLine } from '../../../common/config';
import { CardId, StepId } from '../../components/get_started/types';
import { storage } from '../storage';
export const ACTIVE_PRODUCTS_STORAGE_KEY = 'ACTIVE_PRODUCTS';
@ -13,12 +14,12 @@ export const FINISHED_STEPS_STORAGE_KEY = 'FINISHED_STEPS';
export const getStartedStorage = {
getActiveProductsFromStorage: () => {
const activeProducts: ProductId[] = storage.get(ACTIVE_PRODUCTS_STORAGE_KEY);
const activeProducts: ProductLine[] = storage.get(ACTIVE_PRODUCTS_STORAGE_KEY);
return activeProducts ?? new Array();
},
toggleActiveProductsInStorage: (productId: ProductId) => {
const activeProducts: ProductId[] =
storage.get(ACTIVE_PRODUCTS_STORAGE_KEY) ?? new Array<ProductId>();
toggleActiveProductsInStorage: (productId: ProductLine) => {
const activeProducts: ProductLine[] =
storage.get(ACTIVE_PRODUCTS_STORAGE_KEY) ?? new Array<ProductLine>();
const index = activeProducts.indexOf(productId);
if (index < 0) {
activeProducts.push(productId);

View file

@ -48,7 +48,9 @@ export class ServerlessSecurityPlugin
const { securitySolution, serverless } = startDeps;
securitySolution.setIsSidebarEnabled(false);
securitySolution.setGetStartedPage(getSecurityGetStartedComponent(core, startDeps));
securitySolution.setGetStartedPage(
getSecurityGetStartedComponent(core, startDeps, this.config.productTypes)
);
serverless.setProjectHome('/app/security');
serverless.setSideNavComponent(getSecuritySideNavComponent(core, startDeps));