[Guided onboarding] Update guide IDs (#147348)

## Summary

Fixes https://github.com/elastic/kibana/issues/144452

This PR updates the guide IDs used in guided onboarding from generic
values (`observability` and `security`) to more specific values
(`kubernetes` and `siem`) which will allow us to add more guides for
observability and security in the future.


### Checklist

- [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
This commit is contained in:
Yulia Čech 2022-12-14 15:51:51 +01:00 committed by GitHub
parent edc8624651
commit d1e7f50426
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 181 additions and 170 deletions

View file

@ -24,6 +24,7 @@ import {
EuiSpacer,
EuiText,
EuiTitle,
EuiSelectOption,
} from '@elastic/eui';
import type { GuideState, GuideStepIds, GuideId, GuideStep } from '@kbn/guided-onboarding';
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
@ -33,6 +34,11 @@ interface MainProps {
notifications: CoreStart['notifications'];
}
const exampleGuideIds: GuideId[] = ['search', 'siem', 'kubernetes', 'testGuide'];
const selectOptions: EuiSelectOption[] = exampleGuideIds.map((guideId) => ({
value: guideId,
text: guideId,
}));
export const Main = (props: MainProps) => {
const {
guidedOnboarding: { guidedOnboardingApi },
@ -42,7 +48,7 @@ export const Main = (props: MainProps) => {
const [guidesState, setGuidesState] = useState<GuideState[] | undefined>(undefined);
const [activeGuide, setActiveGuide] = useState<GuideState | undefined>(undefined);
const [selectedGuide, setSelectedGuide] = useState<GuideId | undefined>('observability');
const [selectedGuide, setSelectedGuide] = useState<GuideId | undefined>('kubernetes');
const [selectedStep, setSelectedStep] = useState<GuideStepIds | undefined>(undefined);
useEffect(() => {
@ -206,7 +212,7 @@ export const Main = (props: MainProps) => {
</EuiText>
<EuiSpacer />
<EuiFlexGroup>
{(['search', 'security', 'observability', 'testGuide'] as GuideId[]).map((guideId) => {
{exampleGuideIds.map((guideId) => {
const guideState = guidesState?.find((guide) => guide.guideId === guideId);
return (
<EuiFlexItem>
@ -266,12 +272,7 @@ export const Main = (props: MainProps) => {
<EuiFormRow label="Guide" helpText="Select a guide">
<EuiSelect
id="guideSelect"
options={[
{ value: 'observability', text: 'observability' },
{ value: 'security', text: 'security' },
{ value: 'search', text: 'search' },
{ value: 'testGuide', text: 'test guide' },
]}
options={selectOptions}
value={selectedGuide}
onChange={(e) => {
const value = e.target.value as GuideId;

View file

@ -14,5 +14,5 @@ export type {
GuideStep,
GuideStatus,
} from './src/types';
export { GuideCard, ObservabilityLinkCard } from './src/components/landing_page';
export type { UseCase } from './src/components/landing_page';
export { GuideCard, InfrastructureLinkCard } from './src/components/landing_page';
export type { GuideCardUseCase } from './src/components/landing_page';

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`guide card snapshots should render use case card component for observability 1`] = `
exports[`guide card snapshots should render use case card component for kubernetes 1`] = `
<UseCaseCard
addBasePath={[MockFunction]}
description="Monitor your Kubernetes infrastructure by consolidating your logs and metrics."
@ -8,12 +8,13 @@ exports[`guide card snapshots should render use case card component for observab
<GuideCardFooter
activateGuide={[MockFunction]}
guides={Array []}
useCase="observability"
telemetryId="kubernetes"
useCase="kubernetes"
/>
}
isDarkTheme={false}
title="Observe my Kubernetes infrastructure"
useCase="observability"
useCase="kubernetes"
/>
`;
@ -25,6 +26,7 @@ exports[`guide card snapshots should render use case card component for search 1
<GuideCardFooter
activateGuide={[MockFunction]}
guides={Array []}
telemetryId="search"
useCase="search"
/>
}
@ -34,7 +36,7 @@ exports[`guide card snapshots should render use case card component for search 1
/>
`;
exports[`guide card snapshots should render use case card component for security 1`] = `
exports[`guide card snapshots should render use case card component for siem 1`] = `
<UseCaseCard
addBasePath={[MockFunction]}
description="Investigate threats and get your SIEM up and running by installing the Elastic Defend integration."
@ -42,11 +44,12 @@ exports[`guide card snapshots should render use case card component for security
<GuideCardFooter
activateGuide={[MockFunction]}
guides={Array []}
useCase="security"
telemetryId="siem"
useCase="siem"
/>
}
isDarkTheme={false}
title="Protect my environment"
useCase="security"
useCase="siem"
/>
`;

View file

@ -25,6 +25,6 @@ exports[`observability link card snapshots should render link card for observabi
}
isDarkTheme={false}
title="Observe my data"
useCase="observability"
useCase="infrastructure"
/>
`;

View file

@ -26,13 +26,13 @@ describe('guide card', () => {
expect(component).toMatchSnapshot();
});
test('should render use case card component for observability', async () => {
const component = await shallow(<GuideCard {...defaultProps} useCase="observability" />);
test('should render use case card component for kubernetes', async () => {
const component = await shallow(<GuideCard {...defaultProps} useCase="kubernetes" />);
expect(component).toMatchSnapshot();
});
test('should render use case card component for security', async () => {
const component = await shallow(<GuideCard {...defaultProps} useCase="security" />);
test('should render use case card component for siem', async () => {
const component = await shallow(<GuideCard {...defaultProps} useCase="siem" />);
expect(component).toMatchSnapshot();
});

View file

@ -11,16 +11,23 @@ import { i18n } from '@kbn/i18n';
import { GuideState } from '../../types';
import { GuideCardFooter } from './guide_card_footer';
import { UseCase, UseCaseCard } from './use_case_card';
import { UseCaseCard } from './use_case_card';
// separate type for GuideCardUseCase that includes some of GuideIds
export type GuideCardUseCase = 'search' | 'kubernetes' | 'siem';
type GuideCardConstants = {
[key in UseCase]: {
[key in GuideCardUseCase]: {
i18nTexts: {
title: string;
description: string;
};
// duplicate the telemetry id from the guide config to not load the config from the endpoint
// this might change if we decide to use the guide config for the cards
// see this issue https://github.com/elastic/kibana/issues/146672
telemetryId: string;
};
};
const constants: GuideCardConstants = {
search: {
i18nTexts: {
@ -35,44 +42,47 @@ const constants: GuideCardConstants = {
}
),
},
telemetryId: 'search',
},
observability: {
kubernetes: {
i18nTexts: {
title: i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.observability.cardTitle',
'guidedOnboardingPackage.gettingStarted.guideCard.kubernetes.cardTitle',
{
defaultMessage: 'Observe my Kubernetes infrastructure',
}
),
description: i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.observability.cardDescription',
'guidedOnboardingPackage.gettingStarted.guideCard.kubernetes.cardDescription',
{
defaultMessage:
'Monitor your Kubernetes infrastructure by consolidating your logs and metrics.',
}
),
},
telemetryId: 'kubernetes',
},
security: {
siem: {
i18nTexts: {
title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.security.cardTitle', {
title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.siem.cardTitle', {
defaultMessage: 'Protect my environment',
}),
description: i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.security.cardDescription',
'guidedOnboardingPackage.gettingStarted.guideCard.siem.cardDescription',
{
defaultMessage:
'Investigate threats and get your SIEM up and running by installing the Elastic Defend integration.',
}
),
},
telemetryId: 'siem',
},
};
export interface GuideCardProps {
useCase: UseCase;
useCase: GuideCardUseCase;
guides: GuideState[];
activateGuide: (useCase: UseCase, guide?: GuideState) => Promise<void>;
activateGuide: (useCase: GuideCardUseCase, guide?: GuideState) => Promise<void>;
isDarkTheme: boolean;
addBasePath: (url: string) => string;
}
@ -88,7 +98,14 @@ export const GuideCard = ({
useCase={useCase}
title={constants[useCase].i18nTexts.title}
description={constants[useCase].i18nTexts.description}
footer={<GuideCardFooter guides={guides} activateGuide={activateGuide} useCase={useCase} />}
footer={
<GuideCardFooter
guides={guides}
activateGuide={activateGuide}
useCase={useCase}
telemetryId={constants[useCase].telemetryId}
/>
}
isDarkTheme={isDarkTheme}
addBasePath={addBasePath}
/>

View file

@ -15,6 +15,7 @@ import { GuideState } from '../../types';
const defaultProps: GuideCardFooterProps = {
guides: [],
useCase: 'search',
telemetryId: 'search',
activateGuide: jest.fn(),
};

View file

@ -11,7 +11,7 @@ import { css } from '@emotion/react';
import { EuiButton, EuiProgress, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { GuideId, GuideState } from '../../types';
import { UseCase } from './use_case_card';
import type { GuideCardUseCase } from './guide_card';
const viewGuideLabel = i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel',
@ -48,17 +48,23 @@ const progressBarLabelCss = css`
export interface GuideCardFooterProps {
guides: GuideState[];
useCase: UseCase;
activateGuide: (useCase: UseCase, guideState?: GuideState) => void;
useCase: GuideCardUseCase;
telemetryId: string;
activateGuide: (useCase: GuideCardUseCase, guideState?: GuideState) => void;
}
export const GuideCardFooter = ({ guides, useCase, activateGuide }: GuideCardFooterProps) => {
export const GuideCardFooter = ({
guides,
useCase,
telemetryId,
activateGuide,
}: GuideCardFooterProps) => {
const guideState = guides.find((guide) => guide.guideId === (useCase as GuideId));
const viewGuideButton = (
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<EuiButton
// Used for FS tracking
data-test-subj={`onboarding--guideCard--view--${useCase}`}
data-test-subj={`onboarding--guideCard--view--${telemetryId}`}
fill
onClick={() => activateGuide(useCase, guideState)}
>
@ -117,7 +123,7 @@ export const GuideCardFooter = ({ guides, useCase, activateGuide }: GuideCardFoo
<EuiFlexItem grow={false}>
<EuiButton
// Used for FS tracking
data-test-subj={`onboarding--guideCard--continue--${useCase}`}
data-test-subj={`onboarding--guideCard--continue--${telemetryId}`}
fill
onClick={() => activateGuide(useCase, guideState)}
>

View file

@ -7,5 +7,6 @@
*/
export { GuideCard } from './guide_card';
export { ObservabilityLinkCard } from './observability_link_card';
export type { GuideCardUseCase } from './guide_card';
export { InfrastructureLinkCard } from './infrastructure_link_card';
export type { UseCase } from './use_case_card';

View file

@ -8,7 +8,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ObservabilityLinkCard } from './observability_link_card';
import { InfrastructureLinkCard } from './infrastructure_link_card';
const defaultProps = {
navigateToApp: jest.fn(),
@ -19,7 +19,7 @@ const defaultProps = {
describe('observability link card', () => {
describe('snapshots', () => {
test('should render link card for observability', async () => {
const component = await shallow(<ObservabilityLinkCard {...defaultProps} />);
const component = await shallow(<InfrastructureLinkCard {...defaultProps} />);
expect(component).toMatchSnapshot();
});
});

View file

@ -13,7 +13,7 @@ import type { NavigateToAppOptions } from '@kbn/core-application-browser';
import { UseCaseCard } from './use_case_card';
interface LinkCardConstants {
observability: {
infrastructure: {
i18nTexts: {
title: string;
description: string;
@ -22,16 +22,16 @@ interface LinkCardConstants {
}
const constants: LinkCardConstants = {
observability: {
infrastructure: {
i18nTexts: {
title: i18n.translate(
'guidedOnboardingPackage.gettingStarted.linkCard.observability.cardTitle',
'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.cardTitle',
{
defaultMessage: 'Observe my data',
}
),
description: i18n.translate(
'guidedOnboardingPackage.gettingStarted.linkCard.observability.cardDescription',
'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.cardDescription',
{
defaultMessage:
'Add application, infrastructure, and user data through our pre-built integrations.',
@ -41,7 +41,7 @@ const constants: LinkCardConstants = {
},
};
export const ObservabilityLinkCard = ({
export const InfrastructureLinkCard = ({
navigateToApp,
isDarkTheme,
addBasePath,
@ -64,18 +64,21 @@ export const ObservabilityLinkCard = ({
fill
onClick={navigateToIntegrations}
>
{i18n.translate('guidedOnboardingPackage.gettingStarted.linkCard.buttonLabel', {
defaultMessage: 'View integrations',
})}
{i18n.translate(
'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.buttonLabel',
{
defaultMessage: 'View integrations',
}
)}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
return (
<UseCaseCard
useCase={'observability'}
title={constants.observability.i18nTexts.title}
description={constants.observability.i18nTexts.description}
useCase={'infrastructure'}
title={constants.infrastructure.i18nTexts.title}
description={constants.infrastructure.i18nTexts.description}
footer={button}
isDarkTheme={isDarkTheme}
addBasePath={addBasePath}

View file

@ -9,11 +9,13 @@
import React, { ReactNode } from 'react';
import { EuiCard, EuiText, EuiImage } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { GuideCardUseCase } from './guide_card';
type UseCaseConstants = {
[key in UseCase]: {
logAltText: string;
betaBadgeLabel: string;
imageUrlPrefix: string;
};
};
const constants: UseCaseConstants = {
@ -24,32 +26,44 @@ const constants: UseCaseConstants = {
betaBadgeLabel: i18n.translate('guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel', {
defaultMessage: 'search',
}),
imageUrlPrefix: '/plugins/home/assets/solution_logos/search',
},
observability: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.observability.iconName', {
kubernetes: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.kubernetes.iconName', {
defaultMessage: 'Observability logo',
}),
betaBadgeLabel: i18n.translate(
'guidedOnboardingPackage.gettingStarted.observability.betaBadgeLabel',
'guidedOnboardingPackage.gettingStarted.kubernetes.betaBadgeLabel',
{
defaultMessage: 'observe',
}
),
imageUrlPrefix: '/plugins/home/assets/solution_logos/observability',
},
security: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.security.iconName', {
defaultMessage: 'Security logo',
infrastructure: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.infrastructure.iconName', {
defaultMessage: 'Observability logo',
}),
betaBadgeLabel: i18n.translate(
'guidedOnboardingPackage.gettingStarted.security.betaBadgeLabel',
'guidedOnboardingPackage.gettingStarted.infrastructure.betaBadgeLabel',
{
defaultMessage: 'protect',
defaultMessage: 'observe',
}
),
imageUrlPrefix: '/plugins/home/assets/solution_logos/observability',
},
siem: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.siem.iconName', {
defaultMessage: 'Security logo',
}),
betaBadgeLabel: i18n.translate('guidedOnboardingPackage.gettingStarted.siem.betaBadgeLabel', {
defaultMessage: 'protect',
}),
imageUrlPrefix: '/plugins/home/assets/solution_logos/security',
},
};
export type UseCase = 'search' | 'observability' | 'security';
export type UseCase = GuideCardUseCase | 'infrastructure';
export interface UseCaseCardProps {
useCase: UseCase;
@ -68,11 +82,8 @@ export const UseCaseCard = ({
isDarkTheme,
addBasePath,
}: UseCaseCardProps) => {
const getImageUrl = (imageName: UseCase) => {
const imagePath = `/plugins/home/assets/solution_logos/${imageName}${
isDarkTheme ? '_dark' : ''
}.png`;
const getImageUrl = (imageUrlPrefix: string) => {
const imagePath = `${imageUrlPrefix}${isDarkTheme ? '_dark' : ''}.png`;
return addBasePath(imagePath);
};
@ -86,7 +97,12 @@ export const UseCaseCard = ({
return (
<EuiCard
image={<EuiImage src={getImageUrl(useCase)} alt={constants[useCase].logAltText} />}
image={
<EuiImage
src={getImageUrl(constants[useCase].imageUrlPrefix)}
alt={constants[useCase].logAltText}
/>
}
title={titleElement}
description={description}
footer={footer}

View file

@ -6,14 +6,14 @@
* Side Public License, v 1.
*/
export type GuideId = 'observability' | 'security' | 'search' | 'testGuide';
export type GuideId = 'kubernetes' | 'siem' | 'search' | 'testGuide';
type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability';
type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases';
type KubernetesStepIds = 'add_data' | 'view_dashboard' | 'tour_observability';
type SiemStepIds = 'add_data' | 'rules' | 'alertsCases';
type SearchStepIds = 'add_data' | 'search_experience';
type TestGuideIds = 'step1' | 'step2' | 'step3';
export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds | TestGuideIds;
export type GuideStepIds = KubernetesStepIds | SiemStepIds | SearchStepIds | TestGuideIds;
export interface GuideState {
guideId: GuideId;

View file

@ -14,6 +14,7 @@ export const testGuideConfig: GuideConfig = {
title: 'Test guide for development',
description: `This guide is used to test the guided onboarding UI while in development and to run automated tests for the API and UI components.`,
guideName: 'Testing example',
telemetryId: 'testGuide',
completedGuideRedirectLocation: {
appID: 'guidedOnboardingExample',
path: '/',

View file

@ -56,6 +56,7 @@ export interface GuideConfig {
title: string;
description: string;
guideName: string;
telemetryId: string;
docs?: {
text: string;
url: string;

View file

@ -59,20 +59,6 @@ const getProgress = (state?: GuideState): number => {
return 0;
};
// Temporarily provide a different guide ID for telemetry purposes
// Should not be necessary once https://github.com/elastic/kibana/issues/144452 is addressed
const getTelemetryGuideId = (guideId?: GuideId) => {
switch (guideId) {
case 'security':
return 'siem';
case 'observability':
return 'kubernetes';
case 'search':
default:
return guideId;
}
};
export const GuidePanel = ({ api, application, notifications }: GuidePanelProps) => {
const { euiTheme } = useEuiTheme();
const [isGuideOpen, setIsGuideOpen] = useState(false);
@ -186,7 +172,6 @@ export const GuidePanel = ({ api, application, notifications }: GuidePanelProps)
const stepsCompleted = getProgress(pluginState?.activeGuide);
const isGuideReadyToComplete = pluginState?.activeGuide?.status === 'ready_to_complete';
const telemetryGuideId = getTelemetryGuideId(pluginState?.activeGuide?.guideId);
return (
<>
@ -311,7 +296,7 @@ export const GuidePanel = ({ api, application, notifications }: GuidePanelProps)
stepNumber={index + 1}
handleButtonClick={() => handleStepButtonClick(stepState, step)}
key={accordionId}
telemetryGuideId={telemetryGuideId!}
telemetryGuideId={guideConfig!.telemetryId}
/>
);
}
@ -324,7 +309,9 @@ export const GuidePanel = ({ api, application, notifications }: GuidePanelProps)
onClick={() => completeGuide(guideConfig.completedGuideRedirectLocation)}
fill
// data-test-subj used for FS tracking and testing
data-test-subj={`onboarding--completeGuideButton--${telemetryGuideId}`}
data-test-subj={`onboarding--completeGuideButton--${
guideConfig!.telemetryId
}`}
>
{i18n.translate('guidedOnboarding.dropdownPanel.elasticButtonLabel', {
defaultMessage: 'Continue using Elastic',
@ -405,7 +392,7 @@ export const GuidePanel = ({ api, application, notifications }: GuidePanelProps)
<QuitGuideModal
closeModal={closeQuitGuideModal}
currentGuide={pluginState!.activeGuide!}
telemetryGuideId={telemetryGuideId!}
telemetryGuideId={guideConfig!.telemetryId}
notifications={notifications}
/>
)}

View file

@ -213,7 +213,7 @@ describe('GuidedOnboarding ApiService', () => {
});
it('returns undefined if the selected guide is not active', async () => {
const completedState = await apiService.completeGuide('observability'); // not active
const completedState = await apiService.completeGuide('kubernetes'); // not active
expect(completedState).not.toBeDefined();
});
@ -282,7 +282,7 @@ describe('GuidedOnboarding ApiService', () => {
});
it('returns undefined if the selected guide is not active', async () => {
const startState = await apiService.startGuideStep('observability', 'add_data'); // not active
const startState = await apiService.startGuideStep('kubernetes', 'add_data'); // not active
expect(startState).not.toBeDefined();
});
});
@ -400,7 +400,7 @@ describe('GuidedOnboarding ApiService', () => {
});
it('returns undefined if the selected guide is not active', async () => {
const startState = await apiService.completeGuideStep('observability', 'add_data'); // not active
const startState = await apiService.completeGuideStep('kubernetes', 'add_data'); // not active
expect(startState).not.toBeDefined();
});
});

View file

@ -36,7 +36,7 @@ describe('GuidedOnboarding ApiService helpers', () => {
it('returns undefined if the config is not found', () => {
const config = findGuideConfigByGuideId(
{ testGuide: testGuideConfig } as GuidesConfig,
'security'
'siem'
);
expect(config).toBeUndefined();
});
@ -119,7 +119,7 @@ describe('GuidedOnboarding ApiService helpers', () => {
});
it('returns false if guide is not in progress', () => {
const isActive = isGuideActive(mockPluginStateInProgress, 'security');
const isActive = isGuideActive(mockPluginStateInProgress, 'siem');
expect(isActive).toBe(false);
});
@ -138,7 +138,7 @@ describe('GuidedOnboarding ApiService helpers', () => {
it('returns false if guide is not active', () => {
const isInProgress = isStepInProgress(
testGuideStep1InProgressState,
'security',
'siem',
testGuideFirstStep
);
expect(isInProgress).toBe(false);
@ -172,7 +172,7 @@ describe('GuidedOnboarding ApiService helpers', () => {
it('returns false if guide is not active', () => {
const isReadyToComplete = isStepReadyToComplete(
testGuideStep1InProgressState,
'security',
'siem',
testGuideFirstStep
);
expect(isReadyToComplete).toBe(false);

View file

@ -61,34 +61,34 @@ exports[`getting started should render getting started component 1`] = `
/>
</EuiFlexItem>
<EuiFlexItem
key="guideCard-observability"
key="guideCard-kubernetes"
>
<GuideCard
activateGuide={[Function]}
addBasePath={[Function]}
guides={Array []}
isDarkTheme={false}
useCase="observability"
useCase="kubernetes"
/>
</EuiFlexItem>
<EuiFlexItem
key="linkCard-observabilityLink"
key="linkCard-infrastructure"
>
<ObservabilityLinkCard
<InfrastructureLinkCard
addBasePath={[Function]}
isDarkTheme={false}
navigateToApp={[MockFunction]}
/>
</EuiFlexItem>
<EuiFlexItem
key="guideCard-security"
key="guideCard-siem"
>
<GuideCard
activateGuide={[Function]}
addBasePath={[Function]}
guides={Array []}
isDarkTheme={false}
useCase="security"
useCase="siem"
/>
</EuiFlexItem>
</EuiFlexGrid>

View file

@ -26,8 +26,9 @@ import { useHistory } from 'react-router-dom';
import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import type { GuideState, GuideId, UseCase } from '@kbn/guided-onboarding';
import { GuideCard, ObservabilityLinkCard } from '@kbn/guided-onboarding';
import type { GuideState, GuideId } from '@kbn/guided-onboarding';
import { GuideCard, InfrastructureLinkCard } from '@kbn/guided-onboarding';
import type { GuideCardUseCase } from '@kbn/guided-onboarding';
import { getServices } from '../../kibana_services';
import { KEY_ENABLE_WELCOME } from '../home';
@ -113,7 +114,7 @@ export const GettingStarted = () => {
`;
const isDarkTheme = uiSettings.get<boolean>('theme:darkMode');
const activateGuide = async (useCase: UseCase, guideState?: GuideState) => {
const activateGuide = async (useCase: GuideCardUseCase, guideState?: GuideState) => {
try {
await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState);
} catch (err) {
@ -196,11 +197,11 @@ export const GettingStarted = () => {
<EuiSpacer size="s" />
<EuiSpacer size="xxl" />
<EuiFlexGrid columns={4} gutterSize="l">
{['search', 'observability', 'observabilityLink', 'security'].map((useCase) => {
if (useCase === 'observabilityLink') {
{['search', 'kubernetes', 'infrastructure', 'siem'].map((useCase) => {
if (useCase === 'infrastructure') {
return (
<EuiFlexItem key={`linkCard-${useCase}`}>
<ObservabilityLinkCard
<InfrastructureLinkCard
navigateToApp={application.navigateToApp}
isDarkTheme={isDarkTheme}
addBasePath={http.basePath.prepend}
@ -211,7 +212,7 @@ export const GettingStarted = () => {
return (
<EuiFlexItem key={`guideCard-${useCase}`}>
<GuideCard
useCase={useCase as UseCase}
useCase={useCase as GuideCardUseCase}
guides={guidesState}
activateGuide={activateGuide}
isDarkTheme={isDarkTheme}

View file

@ -15,7 +15,7 @@ export default function testGetGuideConfig({ getService }: FtrProviderContext) {
describe('GET /api/guided_onboarding/configs', () => {
// check that production guides are present
['security', 'search', 'observability'].map((guideId) => {
['siem', 'search', 'kubernetes'].map((guideId) => {
it(`returns config for ${guideId}`, async () => {
const response = await supertest.get(`${getConfigsPath}/${guideId}`).expect(200);
expect(response.body).not.to.be.empty();

View file

@ -136,7 +136,7 @@ export default function testPutState({ getService }: FtrProviderContext) {
.send({
guide: {
...testGuideStep1ActiveState,
guideId: 'observability',
guideId: 'kubernetes',
},
})
.expect(200);
@ -154,11 +154,11 @@ export default function testPutState({ getService }: FtrProviderContext) {
});
expect(searchGuideSO.attributes.isActive).to.eql(false);
const observabilityGuide = await kibanaServer.savedObjects.get({
const kubernetesGuide = await kibanaServer.savedObjects.get({
type: guideStateSavedObjectsType,
id: 'observability',
id: 'kubernetes',
});
expect(observabilityGuide.attributes.isActive).to.eql(true);
expect(kubernetesGuide.attributes.isActive).to.eql(true);
});
});
}

View file

@ -18,6 +18,7 @@ export const searchGuideConfig: GuideConfig = {
'Build custom search experiences with your data using Elastics out-of-the-box web crawler, connectors, and robust APIs. Gain deep insights from the built-in search analytics to curate results and optimize relevance.',
}),
guideName: 'Enterprise Search',
telemetryId: 'search',
steps: [
{
id: 'add_data',

View file

@ -8,8 +8,8 @@
import type { GuideConfig } from '@kbn/guided-onboarding-plugin/common';
import { i18n } from '@kbn/i18n';
export const observabilityGuideId = 'observability';
export const observabilityGuideConfig: GuideConfig = {
export const kubernetesGuideId = 'kubernetes';
export const kubernetesGuideConfig: GuideConfig = {
title: i18n.translate('xpack.observability.guideConfig.title', {
defaultMessage: 'Observe my Kubernetes infrastructure',
}),
@ -17,6 +17,7 @@ export const observabilityGuideConfig: GuideConfig = {
defaultMessage: `We'll help you quickly get visibility into your Kubernetes environment with our Elastic integration. Gain deep insights from your logs, metrics, and traces to proactively detect issues and take action to resolve them.`,
}),
guideName: 'Kubernetes',
telemetryId: 'kubernetes',
docs: {
text: i18n.translate('xpack.observability.guideConfig.documentationLink', {
defaultMessage: 'Learn more',

View file

@ -173,7 +173,7 @@ export function ObservabilityTour({
const isGuidedOnboardingActive = useObservable(
// if guided onboarding is not available, return false
guidedOnboardingApi
? guidedOnboardingApi.isGuideStepActive$('observability', 'tour_observability')
? guidedOnboardingApi.isGuideStepActive$('kubernetes', 'tour_observability')
: of(false)
);
@ -193,7 +193,7 @@ export function ObservabilityTour({
const endTour = useCallback(async () => {
// Mark the onboarding guide step as complete
if (guidedOnboardingApi) {
await guidedOnboardingApi.completeGuideStep('observability', 'tour_observability');
await guidedOnboardingApi.completeGuideStep('kubernetes', 'tour_observability');
}
// Reset EuiTour step state
setActiveStep(1);

View file

@ -24,9 +24,9 @@ import { ECS_COMPONENT_TEMPLATE_NAME } from '@kbn/rule-registry-plugin/common/as
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
import {
observabilityGuideId,
observabilityGuideConfig,
} from '../common/guided_onboarding/observability_guide_config';
kubernetesGuideId,
kubernetesGuideConfig,
} from '../common/guided_onboarding/kubernetes_guide_config';
import { ObservabilityConfig } from '.';
import {
bootstrapAnnotations,
@ -191,7 +191,7 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
/**
* Register a config for the observability guide
*/
plugins.guidedOnboarding.registerGuideConfig(observabilityGuideId, observabilityGuideConfig);
plugins.guidedOnboarding.registerGuideConfig(kubernetesGuideId, kubernetesGuideConfig);
return {
getAlertDetailsConfig() {

View file

@ -8,12 +8,13 @@
import type { GuideConfig } from '@kbn/guided-onboarding-plugin/common';
import { i18n } from '@kbn/i18n';
export const securityGuideId = 'security';
export const securityGuideConfig: GuideConfig = {
export const siemGuideId = 'siem';
export const siemGuideConfig: GuideConfig = {
title: i18n.translate('xpack.securitySolution.guideConfig.title', {
defaultMessage: 'Elastic Security guided setup',
}),
guideName: 'Security',
telemetryId: 'siem',
completedGuideRedirectLocation: {
appID: 'securitySolutionUI',
path: '/dashboards',

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { siemGuideId } from '../../../common/guided_onboarding/siem_guide_config';
const alertsGuideActiveState = {
isActive: true,
status: 'in_progress',
@ -13,7 +15,7 @@ const alertsGuideActiveState = {
{ id: 'rules', status: 'complete' },
{ id: 'alertsCases', status: 'active' },
],
guideId: 'security',
guideId: siemGuideId,
};
export const startAlertsCasesTour = () =>

View file

@ -7,6 +7,7 @@
import { act, renderHook } from '@testing-library/react-hooks';
import { of } from 'rxjs';
import { siemGuideId } from '../../../../common/guided_onboarding/siem_guide_config';
import { TourContextProvider, useTourContext } from './tour';
import { SecurityStepId, securityTourConfig } from './tour_config';
import { useKibana } from '../../lib/kibana';
@ -71,7 +72,7 @@ describe('useTourContext', () => {
});
await waitForNextUpdate();
result.current.endTourStep(tourId);
expect(mockCompleteGuideStep).toHaveBeenCalledWith('security', tourId);
expect(mockCompleteGuideStep).toHaveBeenCalledWith(siemGuideId, tourId);
});
});
it('activeStep is initially 1', () => {

View file

@ -11,6 +11,7 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useS
import useObservable from 'react-use/lib/useObservable';
import { catchError, of, timeout } from 'rxjs';
import { useLocation } from 'react-router-dom';
import { siemGuideId } from '../../../../common/guided_onboarding/siem_guide_config';
import { isTourPath } from '../../../helpers';
import { useKibana } from '../../lib/kibana';
import type { AlertsCasesTourSteps } from './tour_config';
@ -38,7 +39,7 @@ export const RealTourContextProvider = ({ children }: { children: ReactChild })
const { guidedOnboardingApi } = useKibana().services.guidedOnboarding;
const isRulesTourActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$('security', SecurityStepId.rules).pipe(
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, SecurityStepId.rules).pipe(
// if no result after 30s the observable will error, but the error handler will just emit false
timeout(30000),
catchError((error) => of(false))
@ -46,7 +47,7 @@ export const RealTourContextProvider = ({ children }: { children: ReactChild })
false
);
const isAlertsCasesTourActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$('security', SecurityStepId.alertsCases).pipe(
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, SecurityStepId.alertsCases).pipe(
// if no result after 30s the observable will error, but the error handler will just emit false
timeout(30000),
catchError((error) => of(false))
@ -83,7 +84,7 @@ export const RealTourContextProvider = ({ children }: { children: ReactChild })
}
let ignore = false;
const complete = async () => {
await guidedOnboardingApi.completeGuideStep('security', completeStep);
await guidedOnboardingApi.completeGuideStep(siemGuideId, completeStep);
if (!ignore) {
setCompleteStep(null);
_setActiveStep(1);

View file

@ -9,6 +9,7 @@ import { EuiText, EuiTourStep } from '@elastic/eui';
import useObservable from 'react-use/lib/useObservable';
import { of } from 'rxjs';
import React, { useCallback, useEffect, useState } from 'react';
import { siemGuideId } from '../../../../../../common/guided_onboarding/siem_guide_config';
import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../../../../../../common/constants';
import { useKibana } from '../../../../../common/lib/kibana';
import * as i18n from './translations';
@ -30,7 +31,7 @@ export const RulesPageTourComponent: React.FC<Props> = ({ children }) => {
} = useKibana().services;
const isGuidedOnboardingActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$('security', 'rules') ?? of(false),
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, 'rules') ?? of(false),
true
);

View file

@ -11,6 +11,7 @@ import { noop } from 'lodash';
import React, { useCallback, useEffect, useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { of } from 'rxjs';
import { siemGuideId } from '../../../../../../../common/guided_onboarding/siem_guide_config';
import { BulkActionType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
import { useKibana } from '../../../../../../common/lib/kibana';
import { useFindRulesQuery } from '../../../../../rule_management/api/hooks/use_find_rules_query';
@ -48,7 +49,7 @@ export const RulesManagementTour = () => {
const { actions } = useRulesTableContext();
const isRulesStepActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$('security', 'rules') ?? of(false),
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, 'rules') ?? of(false),
false
);
@ -105,7 +106,7 @@ export const RulesManagementTour = () => {
// Synchronize the current "internal" tour step with the global one
useEffect(() => {
if (isRulesStepActive && tourStatus === GuidedOnboardingRulesStatus.completed) {
guidedOnboardingApi?.completeGuideStep('security', 'rules');
guidedOnboardingApi?.completeGuideStep('siem', 'rules');
}
}, [guidedOnboardingApi, isRulesStepActive, tourStatus]);

View file

@ -30,10 +30,7 @@ import { Dataset } from '@kbn/rule-registry-plugin/server';
import type { ListPluginSetup } from '@kbn/lists-plugin/server';
import type { ILicense } from '@kbn/licensing-plugin/server';
import {
securityGuideId,
securityGuideConfig,
} from '../common/guided_onboarding/security_guide_config';
import { siemGuideId, siemGuideConfig } from '../common/guided_onboarding/siem_guide_config';
import {
createEqlAlertType,
createIndicatorMatchAlertType,
@ -405,7 +402,7 @@ export class Plugin implements ISecuritySolutionPlugin {
/**
* Register a config for the security guide
*/
plugins.guidedOnboarding.registerGuideConfig(securityGuideId, securityGuideConfig);
plugins.guidedOnboarding.registerGuideConfig(siemGuideId, siemGuideConfig);
return {};
}

View file

@ -3114,24 +3114,13 @@
"guidedOnboarding.quitGuideModal.quitButtonLabel": "Quitter le guide",
"guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress} étapes",
"guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "Continuer",
"guidedOnboardingPackage.gettingStarted.guideCard.observability.cardDescription": "Monitorez votre infrastructure Kubernetes en consolidant vos logs et indicateurs.",
"guidedOnboardingPackage.gettingStarted.guideCard.observability.cardTitle": "Observer mon infrastructure Kubernetes",
"guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "Terminé",
"guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "En cours",
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "Créez une expérience de recherche pour vos sites web, vos applications, votre contenu sur le lieu de travail, etc.",
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "Rechercher dans mes données",
"guidedOnboardingPackage.gettingStarted.guideCard.security.cardDescription": "Défendez votre environnement contre les menaces en unifiant SIEM, la sécurité des points de terminaison et la sécurité cloud.",
"guidedOnboardingPackage.gettingStarted.guideCard.security.cardTitle": "Protéger mon environnement",
"guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "Afficher le guide",
"guidedOnboardingPackage.gettingStarted.linkCard.buttonLabel": "Voir les intégrations",
"guidedOnboardingPackage.gettingStarted.linkCard.observability.cardDescription": "Ajoutez des données d'application, d'infrastructure et d'utilisateur via nos intégrations prédéfinies.",
"guidedOnboardingPackage.gettingStarted.linkCard.observability.cardTitle": "Observer mes données",
"guidedOnboardingPackage.gettingStarted.observability.betaBadgeLabel": "observe",
"guidedOnboardingPackage.gettingStarted.observability.iconName": "Logo Observability",
"guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "rechercher",
"guidedOnboardingPackage.gettingStarted.search.iconName": "Logo Entreprise Search",
"guidedOnboardingPackage.gettingStarted.security.betaBadgeLabel": "sécurité",
"guidedOnboardingPackage.gettingStarted.security.iconName": "Logo Security",
"home.loadTutorials.requestFailedErrorMessage": "Échec de la requête avec le code de statut : {status}",
"home.tutorial.addDataToKibanaDescription": "En plus d'ajouter {integrationsLink}, vous pouvez essayer l'exemple de données ou charger vos propres données.",
"home.tutorial.noTutorialLabel": "Tutoriel {tutorialId} introuvable",

View file

@ -3112,24 +3112,13 @@
"guidedOnboarding.quitGuideModal.quitButtonLabel": "ガイドを終了",
"guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress}ステップ",
"guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "続行",
"guidedOnboardingPackage.gettingStarted.guideCard.observability.cardDescription": "ログとメトリックを統合して、Kubernetesインフラストラクチャーを監視できます。",
"guidedOnboardingPackage.gettingStarted.guideCard.observability.cardTitle": "Kubernetesインフラストラクチャーの監視",
"guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "完了",
"guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "進行中",
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "Webサイト、アプリケーション、workplaceコンテンツなどに合った、検索エクスペリエンスを作成します。",
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "データを検索",
"guidedOnboardingPackage.gettingStarted.guideCard.security.cardDescription": "SIEM、エンドポイントセキュリティ、クラウドセキュリティを統合することで、環境を脅威から守ります。",
"guidedOnboardingPackage.gettingStarted.guideCard.security.cardTitle": "環境を保護",
"guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "ガイドを表示",
"guidedOnboardingPackage.gettingStarted.linkCard.buttonLabel": "統合を表示",
"guidedOnboardingPackage.gettingStarted.linkCard.observability.cardDescription": "組み込まれた統合を使用して、アプリケーション、インフラストラクチャー、ユーザーデータを追加します。",
"guidedOnboardingPackage.gettingStarted.linkCard.observability.cardTitle": "データの監視",
"guidedOnboardingPackage.gettingStarted.observability.betaBadgeLabel": "監視",
"guidedOnboardingPackage.gettingStarted.observability.iconName": "オブザーバビリティロゴ",
"guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "検索",
"guidedOnboardingPackage.gettingStarted.search.iconName": "エンタープライズ サーチロゴ",
"guidedOnboardingPackage.gettingStarted.security.betaBadgeLabel": "セキュリティ",
"guidedOnboardingPackage.gettingStarted.security.iconName": "セキュリティロゴ",
"home.loadTutorials.requestFailedErrorMessage": "リクエスト失敗、ステータスコード:{status}",
"home.tutorial.addDataToKibanaDescription": "{integrationsLink}を追加するほかに、サンプルデータを試したり、独自のデータをアップロードしたりできます。",
"home.tutorial.noTutorialLabel": "チュートリアル {tutorialId} が見つかりません",

View file

@ -3116,24 +3116,13 @@
"guidedOnboarding.quitGuideModal.quitButtonLabel": "退出指南",
"guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress} 步骤",
"guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "继续",
"guidedOnboardingPackage.gettingStarted.guideCard.observability.cardDescription": "通过整合日志和指标来监测您的 Kubernetes 基础架构。",
"guidedOnboardingPackage.gettingStarted.guideCard.observability.cardTitle": "观察我的 Kubernetes 基础架构",
"guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "已完成",
"guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "进行中",
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "为您的网站、应用程序、工作区内容或期间的任何内容创建搜索体验。",
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "搜索我的数据",
"guidedOnboardingPackage.gettingStarted.guideCard.security.cardDescription": "通过集 SIEM、Endpoint Security 和云安全于一体来保护您的环境,防止其受到威胁。",
"guidedOnboardingPackage.gettingStarted.guideCard.security.cardTitle": "保护我的环境",
"guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "查看指南",
"guidedOnboardingPackage.gettingStarted.linkCard.buttonLabel": "查看集成",
"guidedOnboardingPackage.gettingStarted.linkCard.observability.cardDescription": "通过预构建的集成功能来添加应用程序、基础架构和用户数据。",
"guidedOnboardingPackage.gettingStarted.linkCard.observability.cardTitle": "观察我的数据",
"guidedOnboardingPackage.gettingStarted.observability.betaBadgeLabel": "观察",
"guidedOnboardingPackage.gettingStarted.observability.iconName": "Observability 徽标",
"guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "搜索",
"guidedOnboardingPackage.gettingStarted.search.iconName": "Enterprise Search 徽标",
"guidedOnboardingPackage.gettingStarted.security.betaBadgeLabel": "安全",
"guidedOnboardingPackage.gettingStarted.security.iconName": "安全徽标",
"home.loadTutorials.requestFailedErrorMessage": "请求失败,状态代码:{status}",
"home.tutorial.addDataToKibanaDescription": "除了添加 {integrationsLink} 以外,您还可以试用样例数据或上传自己的数据。",
"home.tutorial.noTutorialLabel": "无法找到教程 {tutorialId}",