mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Guided onboarding] Landing page updates (#149528)
## Summary Fixes https://github.com/elastic/kibana/issues/149179 This PR updates the landing page for guided onboarding according with the latest feedback: - more cards on the page - most cards redirect to different Kibana pages - some cards activate a solution guide - a filter to highlight cards for a specific solution - guide progress labels and completion indicators #### Out of scope for this PR: - telemetry events (will be addressed in https://github.com/elastic/kibana/issues/149273) - turn on a solution filter based on the url params (planned for 8.8+) #### Screenshots/recording Light theme <img width="1552" alt="Screenshot 2023-01-26 at 12 28 25" src="https://user-images.githubusercontent.com/6585477/214825116-dc2f12c6-1436-4d6f-b16f-67a75c3466a5.png"> Dark theme <img width="1535" alt="Screenshot 2023-01-26 at 12 28 51" src="https://user-images.githubusercontent.com/6585477/214825145-ea9552e0-10ac-4e3c-bb48-d2154132d69c.png"> Progress labels and completion indicators <img width="1402" alt="Screenshot 2023-01-26 at 12 26 54" src="https://user-images.githubusercontent.com/6585477/214825184-63753066-20f0-4589-9970-d1a49e481f85.png"> Filter highlighting https://user-images.githubusercontent.com/6585477/214825210-7396c1d9-3ff9-4a2f-9329-7419c5cd5802.mov ### Checklist - [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> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
This commit is contained in:
parent
a63404c48e
commit
486a866b42
31 changed files with 691 additions and 963 deletions
|
@ -17,6 +17,6 @@ export type {
|
|||
StepConfig,
|
||||
StepDescriptionWithLink,
|
||||
} from './src/types';
|
||||
export { GuideCard, InfrastructureLinkCard } from './src/components/landing_page';
|
||||
export type { GuideCardUseCase } from './src/components/landing_page';
|
||||
export { GuideCards, GuideFilters } from './src/components/landing_page';
|
||||
export type { GuideFilterValues } from './src/components/landing_page';
|
||||
export { testGuideId, testGuideConfig } from './src/common/test_guide_config';
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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."
|
||||
footer={
|
||||
<GuideCardFooter
|
||||
activateGuide={[MockFunction]}
|
||||
guides={Array []}
|
||||
telemetryId="kubernetes"
|
||||
useCase="kubernetes"
|
||||
/>
|
||||
}
|
||||
isDarkTheme={false}
|
||||
title="Observe my Kubernetes infrastructure"
|
||||
useCase="kubernetes"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`guide card snapshots should render use case card component for search 1`] = `
|
||||
<UseCaseCard
|
||||
addBasePath={[MockFunction]}
|
||||
description="Create a search experience for your websites, applications, workplace content, or anything in between."
|
||||
footer={
|
||||
<GuideCardFooter
|
||||
activateGuide={[MockFunction]}
|
||||
guides={Array []}
|
||||
telemetryId="search"
|
||||
useCase="search"
|
||||
/>
|
||||
}
|
||||
isDarkTheme={false}
|
||||
title="Search my data"
|
||||
useCase="search"
|
||||
/>
|
||||
`;
|
||||
|
||||
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."
|
||||
footer={
|
||||
<GuideCardFooter
|
||||
activateGuide={[MockFunction]}
|
||||
guides={Array []}
|
||||
telemetryId="siem"
|
||||
useCase="siem"
|
||||
/>
|
||||
}
|
||||
isDarkTheme={false}
|
||||
title="Protect my environment"
|
||||
useCase="siem"
|
||||
/>
|
||||
`;
|
|
@ -1,181 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`guide card footer snapshots should render the footer when the guide has been completed 1`] = `
|
||||
<Fragment>
|
||||
<EuiProgress
|
||||
label="Completed"
|
||||
labelProps={
|
||||
Object {
|
||||
"css": Object {
|
||||
"map": undefined,
|
||||
"name": "x46p4t",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
text-align: 'left';
|
||||
",
|
||||
"toString": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
max={2}
|
||||
size="s"
|
||||
value={1}
|
||||
valueText="1/2 steps"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="onboarding--guideCard--view--search"
|
||||
fill={true}
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
View guide
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`guide card footer snapshots should render the footer when the guide has not started yet 1`] = `
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="onboarding--guideCard--view--search"
|
||||
fill={true}
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
View guide
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
||||
exports[`guide card footer snapshots should render the footer when the guide is in progress 1`] = `
|
||||
<Fragment>
|
||||
<EuiProgress
|
||||
label="In progress"
|
||||
labelProps={
|
||||
Object {
|
||||
"css": Object {
|
||||
"map": undefined,
|
||||
"name": "x46p4t",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
text-align: 'left';
|
||||
",
|
||||
"toString": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
max={2}
|
||||
size="s"
|
||||
value={1}
|
||||
valueText="1/2 steps"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="onboarding--guideCard--continue--search"
|
||||
fill={true}
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
Continue
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`guide card footer snapshots should render the footer when the guide is ready to complete 1`] = `
|
||||
<Fragment>
|
||||
<EuiProgress
|
||||
label="In progress"
|
||||
labelProps={
|
||||
Object {
|
||||
"css": Object {
|
||||
"map": undefined,
|
||||
"name": "x46p4t",
|
||||
"next": undefined,
|
||||
"styles": "
|
||||
text-align: 'left';
|
||||
",
|
||||
"toString": [Function],
|
||||
},
|
||||
}
|
||||
}
|
||||
max={2}
|
||||
size="s"
|
||||
value={1}
|
||||
valueText="1/2 steps"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="l"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="onboarding--guideCard--continue--search"
|
||||
fill={true}
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
Continue
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`guide card footer snapshots should render the footer when the guided onboarding has not started yet 1`] = `
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="onboarding--guideCard--view--search"
|
||||
fill={true}
|
||||
isLoading={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
View guide
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
255
packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap
generated
Normal file
255
packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,255 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`guide cards snapshots should render all cards 1`] = `
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={true}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="0"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"guideId": "search",
|
||||
"order": 1,
|
||||
"solution": "search",
|
||||
"telemetryId": "guided-onboarding--search--application",
|
||||
"title": "Build an application on top of Elasticsearch",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="1"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"navigateTo": Object {
|
||||
"appId": "integrations",
|
||||
"path": "/browse?q=log",
|
||||
},
|
||||
"order": 2,
|
||||
"solution": "observability",
|
||||
"telemetryId": "guided-onboarding--observability--logs",
|
||||
"title": "Collect and analyze my logs",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="2"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"guideId": "siem",
|
||||
"order": 3,
|
||||
"solution": "security",
|
||||
"telemetryId": "guided-onboarding--security--siem",
|
||||
"title": "Detect threats in my data with SIEM",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="3"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"guideId": "search",
|
||||
"order": 4,
|
||||
"solution": "search",
|
||||
"telemetryId": "guided-onboarding--search--website",
|
||||
"title": "Add search to my website",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="4"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"navigateTo": Object {
|
||||
"appId": "home",
|
||||
"path": "#/tutorial/apm",
|
||||
},
|
||||
"order": 5,
|
||||
"solution": "observability",
|
||||
"telemetryId": "guided-onboarding--observability--apm",
|
||||
"title": "Monitor my application performance (APM / tracing)",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="5"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"navigateTo": Object {
|
||||
"appId": "integrations",
|
||||
"path": "/detail/endpoint/overview",
|
||||
},
|
||||
"order": 6,
|
||||
"solution": "security",
|
||||
"telemetryId": "guided-onboarding--security--hosts",
|
||||
"title": "Secure my hosts with endpoint security",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="6"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"guideId": "search",
|
||||
"order": 7,
|
||||
"solution": "search",
|
||||
"telemetryId": "guided-onboarding--search--database",
|
||||
"title": "Search across databases and business systems",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="7"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"navigateTo": Object {
|
||||
"appId": "integrations",
|
||||
"path": "/browse/os_system",
|
||||
},
|
||||
"order": 8,
|
||||
"solution": "observability",
|
||||
"telemetryId": "guided-onboarding--observability--hosts",
|
||||
"title": "Monitor my host metrics",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="8"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"navigateTo": Object {
|
||||
"appId": "integrations",
|
||||
"path": "/detail/cloud_security_posture/overview",
|
||||
},
|
||||
"order": 9,
|
||||
"solution": "security",
|
||||
"telemetryId": "guided-onboarding--security--cloud",
|
||||
"title": "Secure my cloud assets with posture management",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
key="9"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[MockFunction]}
|
||||
activeFilter="all"
|
||||
card={
|
||||
Object {
|
||||
"guideId": "kubernetes",
|
||||
"order": 11,
|
||||
"solution": "observability",
|
||||
"telemetryId": "guided-onboarding--observability--kubernetes",
|
||||
"title": "Monitor Kubernetes clusters",
|
||||
}
|
||||
}
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
|
@ -1,30 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`observability link card snapshots should render link card for observability 1`] = `
|
||||
<UseCaseCard
|
||||
addBasePath={[MockFunction]}
|
||||
description="Add application, infrastructure, and user data through our pre-built integrations."
|
||||
footer={
|
||||
<EuiFlexGroup
|
||||
justifyContent="center"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="onboarding--linkCard--observability"
|
||||
fill={true}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
View integrations
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
isDarkTheme={false}
|
||||
title="Observe my data"
|
||||
useCase="infrastructure"
|
||||
/>
|
||||
`;
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { GuideCard, GuideCardProps } from './guide_card';
|
||||
|
||||
const defaultProps: GuideCardProps = {
|
||||
useCase: 'search',
|
||||
guides: [],
|
||||
activateGuide: jest.fn(),
|
||||
isDarkTheme: false,
|
||||
addBasePath: jest.fn(),
|
||||
};
|
||||
|
||||
describe('guide card', () => {
|
||||
describe('snapshots', () => {
|
||||
test('should render use case card component for search', async () => {
|
||||
const component = await shallow(<GuideCard {...defaultProps} useCase="search" />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
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 siem', async () => {
|
||||
const component = await shallow(<GuideCard {...defaultProps} useCase="siem" />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,108 +6,115 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiTextColor } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { GuideState } from '../../types';
|
||||
import { GuideCardFooter } from './guide_card_footer';
|
||||
import { UseCaseCard } from './use_case_card';
|
||||
import { GuideCardConstants } from './guide_cards.constants';
|
||||
import { GuideCardsProps } from './guide_cards';
|
||||
|
||||
// separate type for GuideCardUseCase that includes some of GuideIds
|
||||
export type GuideCardUseCase = 'search' | 'kubernetes' | 'siem';
|
||||
type GuideCardConstants = {
|
||||
[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 cardCss = css`
|
||||
position: relative;
|
||||
min-height: 110px;
|
||||
width: 380px;
|
||||
.euiCard__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const getProgressLabel = (guideState: GuideState | undefined): string | undefined => {
|
||||
if (!guideState) {
|
||||
return undefined;
|
||||
}
|
||||
const { steps } = guideState;
|
||||
const numberSteps = steps.length;
|
||||
const numberCompleteSteps = steps.filter((step) => step.status === 'complete').length;
|
||||
if (numberCompleteSteps < 1 || numberCompleteSteps === numberSteps) {
|
||||
return undefined;
|
||||
}
|
||||
return i18n.translate('guidedOnboardingPackage.gettingStarted.cards.progressLabel', {
|
||||
defaultMessage: '{numberCompleteSteps} of {numberSteps} steps complete',
|
||||
values: {
|
||||
numberCompleteSteps,
|
||||
numberSteps,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const constants: GuideCardConstants = {
|
||||
search: {
|
||||
i18nTexts: {
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle', {
|
||||
defaultMessage: 'Search my data',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Create a search experience for your websites, applications, workplace content, or anything in between.',
|
||||
}
|
||||
),
|
||||
},
|
||||
telemetryId: 'search',
|
||||
},
|
||||
kubernetes: {
|
||||
i18nTexts: {
|
||||
title: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.kubernetes.cardTitle',
|
||||
{
|
||||
defaultMessage: 'Observe my Kubernetes infrastructure',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.kubernetes.cardDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Monitor your Kubernetes infrastructure by consolidating your logs and metrics.',
|
||||
}
|
||||
),
|
||||
},
|
||||
telemetryId: 'kubernetes',
|
||||
},
|
||||
siem: {
|
||||
i18nTexts: {
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.siem.cardTitle', {
|
||||
defaultMessage: 'Protect my environment',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'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: GuideCardUseCase;
|
||||
guides: GuideState[];
|
||||
activateGuide: (useCase: GuideCardUseCase, guide?: GuideState) => Promise<void>;
|
||||
isDarkTheme: boolean;
|
||||
addBasePath: (url: string) => string;
|
||||
}
|
||||
export const GuideCard = ({
|
||||
useCase,
|
||||
guides,
|
||||
card,
|
||||
guidesState,
|
||||
activateGuide,
|
||||
isDarkTheme,
|
||||
addBasePath,
|
||||
}: GuideCardProps) => {
|
||||
navigateToApp,
|
||||
activeFilter,
|
||||
}: GuideCardsProps & { card: GuideCardConstants }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
let guideState: GuideState | undefined;
|
||||
if (card.guideId) {
|
||||
guideState = guidesState.find((state) => state.guideId === card.guideId);
|
||||
}
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
if (card.guideId) {
|
||||
await activateGuide(card.guideId, guideState);
|
||||
} else if (card.navigateTo) {
|
||||
await navigateToApp(card.navigateTo?.appId, {
|
||||
path: card.navigateTo.path,
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [activateGuide, card.guideId, card.navigateTo, guideState, navigateToApp]);
|
||||
|
||||
const isHighlighted = activeFilter === 'all' || activeFilter === card.solution;
|
||||
const isComplete = guideState && guideState.status === 'complete';
|
||||
const progress = getProgressLabel(guideState);
|
||||
return (
|
||||
<UseCaseCard
|
||||
useCase={useCase}
|
||||
title={constants[useCase].i18nTexts.title}
|
||||
description={constants[useCase].i18nTexts.description}
|
||||
footer={
|
||||
<GuideCardFooter
|
||||
guides={guides}
|
||||
activateGuide={activateGuide}
|
||||
useCase={useCase}
|
||||
telemetryId={constants[useCase].telemetryId}
|
||||
/>
|
||||
<EuiCard
|
||||
isDisabled={isLoading}
|
||||
onClick={onClick}
|
||||
css={cardCss}
|
||||
display={isHighlighted ? undefined : 'transparent'}
|
||||
hasBorder={!isHighlighted}
|
||||
title={
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<h3 style={{ fontWeight: 600 }}>{card.title}</h3>
|
||||
</>
|
||||
}
|
||||
titleSize="xs"
|
||||
betaBadgeProps={{
|
||||
label: card.solution,
|
||||
}}
|
||||
description={
|
||||
<>
|
||||
{progress && (
|
||||
<EuiTextColor color="subdued">
|
||||
<small>{progress}</small>
|
||||
</EuiTextColor>
|
||||
)}
|
||||
{isComplete && (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="checkInCircleFilled" color="success" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<small>
|
||||
{i18n.translate('guidedOnboardingPackage.gettingStarted.cards.completeLabel', {
|
||||
defaultMessage: 'Guide complete',
|
||||
})}
|
||||
</small>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
isDarkTheme={isDarkTheme}
|
||||
addBasePath={addBasePath}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { GuideCardFooter, GuideCardFooterProps } from './guide_card_footer';
|
||||
import { GuideState } from '../../types';
|
||||
|
||||
const defaultProps: GuideCardFooterProps = {
|
||||
guides: [],
|
||||
useCase: 'search',
|
||||
telemetryId: 'search',
|
||||
activateGuide: jest.fn(),
|
||||
};
|
||||
|
||||
const searchGuideState: GuideState = {
|
||||
guideId: 'search',
|
||||
status: 'not_started',
|
||||
steps: [
|
||||
{ id: 'add_data', status: 'complete' },
|
||||
{ id: 'search_experience', status: 'in_progress' },
|
||||
],
|
||||
isActive: true,
|
||||
};
|
||||
describe('guide card footer', () => {
|
||||
describe('snapshots', () => {
|
||||
test('should render the footer when the guided onboarding has not started yet', async () => {
|
||||
const component = await shallow(<GuideCardFooter {...defaultProps} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the footer when the guide has not started yet', async () => {
|
||||
const component = await shallow(
|
||||
<GuideCardFooter {...defaultProps} guides={[searchGuideState]} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the footer when the guide is in progress', async () => {
|
||||
const component = await shallow(
|
||||
<GuideCardFooter
|
||||
{...defaultProps}
|
||||
guides={[{ ...searchGuideState, status: 'in_progress' }]}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the footer when the guide is ready to complete', async () => {
|
||||
const component = await shallow(
|
||||
<GuideCardFooter
|
||||
{...defaultProps}
|
||||
guides={[{ ...searchGuideState, status: 'ready_to_complete' }]}
|
||||
/>
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render the footer when the guide has been completed', async () => {
|
||||
const component = await shallow(
|
||||
<GuideCardFooter {...defaultProps} guides={[{ ...searchGuideState, status: 'complete' }]} />
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
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 type { GuideCardUseCase } from './guide_card';
|
||||
|
||||
const viewGuideLabel = i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'View guide',
|
||||
}
|
||||
);
|
||||
|
||||
const continueGuideLabel = i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'Continue',
|
||||
}
|
||||
);
|
||||
|
||||
const completedLabel = i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel',
|
||||
{
|
||||
defaultMessage: 'Completed',
|
||||
}
|
||||
);
|
||||
|
||||
const inProgressLabel = i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel',
|
||||
{
|
||||
defaultMessage: 'In progress',
|
||||
}
|
||||
);
|
||||
|
||||
// The progress bar is rendered within EuiCard, which centers content by default
|
||||
const progressBarLabelCss = css`
|
||||
text-align: 'left';
|
||||
`;
|
||||
|
||||
export interface GuideCardFooterProps {
|
||||
guides: GuideState[];
|
||||
useCase: GuideCardUseCase;
|
||||
telemetryId: string;
|
||||
activateGuide: (useCase: GuideCardUseCase, guideState?: GuideState) => Promise<void>;
|
||||
}
|
||||
export const GuideCardFooter = ({
|
||||
guides,
|
||||
useCase,
|
||||
telemetryId,
|
||||
activateGuide,
|
||||
}: GuideCardFooterProps) => {
|
||||
const guideState = guides.find((guide) => guide.guideId === (useCase as GuideId));
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const activateGuideCallback = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
await activateGuide(useCase, guideState);
|
||||
setIsLoading(false);
|
||||
}, [activateGuide, guideState, useCase]);
|
||||
const viewGuideButton = (
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={isLoading}
|
||||
// Used for FS tracking
|
||||
data-test-subj={`onboarding--guideCard--view--${telemetryId}`}
|
||||
fill
|
||||
onClick={activateGuideCallback}
|
||||
>
|
||||
{viewGuideLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
// guide has not started yet
|
||||
if (!guideState || guideState.status === 'not_started') {
|
||||
return viewGuideButton;
|
||||
}
|
||||
const { status, steps } = guideState;
|
||||
const numberSteps = steps.length;
|
||||
const numberCompleteSteps = steps.filter((step) => step.status === 'complete').length;
|
||||
const stepsLabel = i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel', {
|
||||
defaultMessage: '{progress} steps',
|
||||
values: {
|
||||
progress: `${numberCompleteSteps}/${numberSteps}`,
|
||||
},
|
||||
});
|
||||
// guide is completed
|
||||
if (status === 'complete') {
|
||||
return (
|
||||
<>
|
||||
<EuiProgress
|
||||
valueText={stepsLabel}
|
||||
value={numberCompleteSteps}
|
||||
max={numberSteps}
|
||||
size="s"
|
||||
label={completedLabel}
|
||||
labelProps={{
|
||||
css: progressBarLabelCss,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
{viewGuideButton}
|
||||
</>
|
||||
);
|
||||
}
|
||||
// guide is in progress or ready to complete
|
||||
return (
|
||||
<>
|
||||
<EuiProgress
|
||||
valueText={stepsLabel}
|
||||
value={numberCompleteSteps}
|
||||
max={numberSteps}
|
||||
size="s"
|
||||
label={inProgressLabel}
|
||||
labelProps={{
|
||||
css: progressBarLabelCss,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
isLoading={isLoading}
|
||||
// Used for FS tracking
|
||||
data-test-subj={`onboarding--guideCard--continue--${telemetryId}`}
|
||||
fill
|
||||
onClick={activateGuideCallback}
|
||||
>
|
||||
{continueGuideLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { GuideId } from '../../..';
|
||||
import { GuideCardSolutions } from './guide_cards';
|
||||
|
||||
export interface GuideCardConstants {
|
||||
solution: GuideCardSolutions;
|
||||
title: string;
|
||||
// if present, guideId indicates which guide is opened when clicking the card
|
||||
guideId?: GuideId;
|
||||
// if present, navigateTo indicates where the user will be redirected, when clicking the card
|
||||
navigateTo?: {
|
||||
appId: string;
|
||||
path?: 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;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export const guideCards: GuideCardConstants[] = [
|
||||
{
|
||||
solution: 'search',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.appSearch.title', {
|
||||
defaultMessage: 'Build an application on top of Elasticsearch',
|
||||
}),
|
||||
guideId: 'search',
|
||||
telemetryId: 'guided-onboarding--search--application',
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
solution: 'search',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.websiteSearch.title', {
|
||||
defaultMessage: 'Add search to my website',
|
||||
}),
|
||||
guideId: 'search',
|
||||
telemetryId: 'guided-onboarding--search--website',
|
||||
order: 4,
|
||||
},
|
||||
{
|
||||
solution: 'search',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.databaseSearch.title', {
|
||||
defaultMessage: 'Search across databases and business systems',
|
||||
}),
|
||||
guideId: 'search',
|
||||
telemetryId: 'guided-onboarding--search--database',
|
||||
order: 7,
|
||||
},
|
||||
{
|
||||
solution: 'observability',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.logsObservability.title', {
|
||||
defaultMessage: 'Collect and analyze my logs',
|
||||
}),
|
||||
navigateTo: {
|
||||
appId: 'integrations',
|
||||
path: '/browse?q=log',
|
||||
},
|
||||
telemetryId: 'guided-onboarding--observability--logs',
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
solution: 'observability',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.apmObservability.title', {
|
||||
defaultMessage: 'Monitor my application performance (APM / tracing)',
|
||||
}),
|
||||
navigateTo: {
|
||||
appId: 'home',
|
||||
path: '#/tutorial/apm',
|
||||
},
|
||||
telemetryId: 'guided-onboarding--observability--apm',
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
solution: 'observability',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.hostsObservability.title', {
|
||||
defaultMessage: 'Monitor my host metrics',
|
||||
}),
|
||||
navigateTo: {
|
||||
appId: 'integrations',
|
||||
path: '/browse/os_system',
|
||||
},
|
||||
telemetryId: 'guided-onboarding--observability--hosts',
|
||||
order: 8,
|
||||
},
|
||||
{
|
||||
solution: 'observability',
|
||||
title: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.cards.kubernetesObservability.title',
|
||||
{
|
||||
defaultMessage: 'Monitor Kubernetes clusters',
|
||||
}
|
||||
),
|
||||
guideId: 'kubernetes',
|
||||
telemetryId: 'guided-onboarding--observability--kubernetes',
|
||||
order: 11,
|
||||
},
|
||||
{
|
||||
solution: 'security',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.siemSecurity.title', {
|
||||
defaultMessage: 'Detect threats in my data with SIEM',
|
||||
}),
|
||||
guideId: 'siem',
|
||||
telemetryId: 'guided-onboarding--security--siem',
|
||||
order: 3,
|
||||
},
|
||||
{
|
||||
solution: 'security',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.hostsSecurity.title', {
|
||||
defaultMessage: 'Secure my hosts with endpoint security',
|
||||
}),
|
||||
navigateTo: {
|
||||
appId: 'integrations',
|
||||
path: '/detail/endpoint/overview',
|
||||
},
|
||||
telemetryId: 'guided-onboarding--security--hosts',
|
||||
order: 6,
|
||||
},
|
||||
{
|
||||
solution: 'security',
|
||||
title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.cloudSecurity.title', {
|
||||
defaultMessage: 'Secure my cloud assets with posture management',
|
||||
}),
|
||||
navigateTo: {
|
||||
appId: 'integrations',
|
||||
path: '/detail/cloud_security_posture/overview',
|
||||
},
|
||||
telemetryId: 'guided-onboarding--security--cloud',
|
||||
order: 9,
|
||||
},
|
||||
].sort((cardA, cardB) => cardA.order - cardB.order) as GuideCardConstants[];
|
|
@ -8,18 +8,20 @@
|
|||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { InfrastructureLinkCard } from './infrastructure_link_card';
|
||||
|
||||
const defaultProps = {
|
||||
import { GuideCards, GuideCardsProps } from './guide_cards';
|
||||
|
||||
const defaultProps: GuideCardsProps = {
|
||||
activateGuide: jest.fn(),
|
||||
navigateToApp: jest.fn(),
|
||||
isDarkTheme: false,
|
||||
addBasePath: jest.fn(),
|
||||
activeFilter: 'all',
|
||||
guidesState: [],
|
||||
};
|
||||
|
||||
describe('observability link card', () => {
|
||||
describe('guide cards', () => {
|
||||
describe('snapshots', () => {
|
||||
test('should render link card for observability', async () => {
|
||||
const component = await shallow(<InfrastructureLinkCard {...defaultProps} />);
|
||||
test('should render all cards', async () => {
|
||||
const component = await shallow(<GuideCards {...defaultProps} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { ApplicationStart } from '@kbn/core-application-browser';
|
||||
|
||||
import { GuideId, GuideState } from '../../types';
|
||||
import { GuideFilterValues } from './guide_filters';
|
||||
import { guideCards } from './guide_cards.constants';
|
||||
import { GuideCard } from './guide_card';
|
||||
|
||||
export type GuideCardSolutions = 'search' | 'observability' | 'security';
|
||||
|
||||
export interface GuideCardsProps {
|
||||
activateGuide: (guideId: GuideId, guideState?: GuideState) => Promise<void>;
|
||||
navigateToApp: ApplicationStart['navigateToApp'];
|
||||
activeFilter: GuideFilterValues;
|
||||
guidesState: GuideState[];
|
||||
}
|
||||
export const GuideCards = (props: GuideCardsProps) => {
|
||||
return (
|
||||
<EuiFlexGroup wrap responsive justifyContent="center">
|
||||
{guideCards.map((card, index) => (
|
||||
<EuiFlexItem key={index} grow={false}>
|
||||
<GuideCard card={card} {...props} />
|
||||
<EuiSpacer size="m" />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { GuideCardSolutions } from './guide_cards';
|
||||
|
||||
const filterButtonCss = css`
|
||||
border-radius: 20px !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0 18px !important;
|
||||
height: 32px !important;
|
||||
&:hover {
|
||||
text-decoration: none !important;
|
||||
transform: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
export type GuideFilterValues = GuideCardSolutions | 'all';
|
||||
interface GuideFiltersProps {
|
||||
activeFilter: GuideFilterValues;
|
||||
setActiveFilter: React.Dispatch<React.SetStateAction<GuideFilterValues>>;
|
||||
}
|
||||
export const GuideFilters = ({ activeFilter, setActiveFilter }: GuideFiltersProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const activeFilterFill = css`
|
||||
background: ${euiTheme.colors.darkestShade};
|
||||
color: ${euiTheme.colors.lightestShade};
|
||||
`;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => setActiveFilter('all')}
|
||||
color="text"
|
||||
css={[filterButtonCss, activeFilter === 'all' && activeFilterFill]}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingPackage.gettingStarted.guideFilter.all.buttonLabel"
|
||||
defaultMessage="All"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => setActiveFilter('search')}
|
||||
color="text"
|
||||
css={[filterButtonCss, activeFilter === 'search' && activeFilterFill]}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingPackage.gettingStarted.guideFilter.search.buttonLabel"
|
||||
defaultMessage="Search"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => setActiveFilter('observability')}
|
||||
color="text"
|
||||
css={[filterButtonCss, activeFilter === 'observability' && activeFilterFill]}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingPackage.gettingStarted.guideFilter.observability.buttonLabel"
|
||||
defaultMessage="Observability"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={() => setActiveFilter('security')}
|
||||
color="text"
|
||||
css={[filterButtonCss, activeFilter === 'security' && activeFilterFill]}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingPackage.gettingStarted.guideFilter.security.buttonLabel"
|
||||
defaultMessage="Security"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -6,7 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { GuideCard } from './guide_card';
|
||||
export type { GuideCardUseCase } from './guide_card';
|
||||
export { InfrastructureLinkCard } from './infrastructure_link_card';
|
||||
export type { UseCase } from './use_case_card';
|
||||
export { GuideCards } from './guide_cards';
|
||||
export { GuideFilters } from './guide_filters';
|
||||
export type { GuideFilterValues } from './guide_filters';
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import type { NavigateToAppOptions } from '@kbn/core-application-browser';
|
||||
import { UseCaseCard } from './use_case_card';
|
||||
|
||||
interface LinkCardConstants {
|
||||
infrastructure: {
|
||||
i18nTexts: {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const constants: LinkCardConstants = {
|
||||
infrastructure: {
|
||||
i18nTexts: {
|
||||
title: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.cardTitle',
|
||||
{
|
||||
defaultMessage: 'Observe my data',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.cardDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Add application, infrastructure, and user data through our pre-built integrations.',
|
||||
}
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const InfrastructureLinkCard = ({
|
||||
navigateToApp,
|
||||
isDarkTheme,
|
||||
addBasePath,
|
||||
}: {
|
||||
navigateToApp: (appId: string, options?: NavigateToAppOptions) => Promise<void>;
|
||||
isDarkTheme: boolean;
|
||||
addBasePath: (url: string) => string;
|
||||
}) => {
|
||||
const navigateToIntegrations = () => {
|
||||
navigateToApp('integrations', {
|
||||
path: '/browse/infrastructure',
|
||||
});
|
||||
};
|
||||
const button = (
|
||||
<EuiFlexGroup justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
// Used for FS tracking
|
||||
data-test-subj={`onboarding--linkCard--observability`}
|
||||
fill
|
||||
onClick={navigateToIntegrations}
|
||||
>
|
||||
{i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.buttonLabel',
|
||||
{
|
||||
defaultMessage: 'View integrations',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
return (
|
||||
<UseCaseCard
|
||||
useCase={'infrastructure'}
|
||||
title={constants.infrastructure.i18nTexts.title}
|
||||
description={constants.infrastructure.i18nTexts.description}
|
||||
footer={button}
|
||||
isDarkTheme={isDarkTheme}
|
||||
addBasePath={addBasePath}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import 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 = {
|
||||
search: {
|
||||
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.search.iconName', {
|
||||
defaultMessage: 'Enterprise Search logo',
|
||||
}),
|
||||
betaBadgeLabel: i18n.translate('guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel', {
|
||||
defaultMessage: 'search',
|
||||
}),
|
||||
imageUrlPrefix: '/plugins/home/assets/solution_logos/search',
|
||||
},
|
||||
kubernetes: {
|
||||
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.kubernetes.iconName', {
|
||||
defaultMessage: 'Observability logo',
|
||||
}),
|
||||
betaBadgeLabel: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.kubernetes.betaBadgeLabel',
|
||||
{
|
||||
defaultMessage: 'observe',
|
||||
}
|
||||
),
|
||||
imageUrlPrefix: '/plugins/home/assets/solution_logos/kubernetes',
|
||||
},
|
||||
infrastructure: {
|
||||
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.infrastructure.iconName', {
|
||||
defaultMessage: 'Observability logo',
|
||||
}),
|
||||
betaBadgeLabel: i18n.translate(
|
||||
'guidedOnboardingPackage.gettingStarted.infrastructure.betaBadgeLabel',
|
||||
{
|
||||
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 = GuideCardUseCase | 'infrastructure';
|
||||
|
||||
export interface UseCaseCardProps {
|
||||
useCase: UseCase;
|
||||
title: string;
|
||||
description: string;
|
||||
footer: ReactNode;
|
||||
isDarkTheme: boolean;
|
||||
addBasePath: (url: string) => string;
|
||||
}
|
||||
|
||||
export const UseCaseCard = ({
|
||||
useCase,
|
||||
title,
|
||||
description,
|
||||
footer,
|
||||
isDarkTheme,
|
||||
addBasePath,
|
||||
}: UseCaseCardProps) => {
|
||||
const getImageUrl = (imageUrlPrefix: string) => {
|
||||
const imagePath = `${imageUrlPrefix}${isDarkTheme ? '_dark' : ''}.png`;
|
||||
return addBasePath(imagePath);
|
||||
};
|
||||
|
||||
const titleElement = (
|
||||
<EuiText textAlign="center">
|
||||
<h4>
|
||||
<strong>{title}</strong>
|
||||
</h4>
|
||||
</EuiText>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiCard
|
||||
image={
|
||||
<EuiImage
|
||||
src={getImageUrl(constants[useCase].imageUrlPrefix)}
|
||||
alt={constants[useCase].logAltText}
|
||||
size={200}
|
||||
margin="s"
|
||||
/>
|
||||
}
|
||||
title={titleElement}
|
||||
description={description}
|
||||
footer={footer}
|
||||
paddingSize="l"
|
||||
betaBadgeProps={{
|
||||
label: constants[useCase].betaBadgeLabel,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -13,7 +13,8 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/i18n",
|
||||
"@kbn/core-application-browser"
|
||||
"@kbn/core-application-browser",
|
||||
"@kbn/i18n-react"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -18,7 +18,7 @@ import { testGuideConfig, testGuideId } from '@kbn/guided-onboarding';
|
|||
|
||||
import type { PluginState } from '../../common';
|
||||
import { API_BASE_PATH } from '../../common';
|
||||
import { apiService } from '../services/api';
|
||||
import { apiService } from '../services/api.service';
|
||||
import type { GuidedOnboardingApi } from '../types';
|
||||
import {
|
||||
testGuideStep1ActiveState,
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { GuideState } from '@kbn/guided-onboarding';
|
||||
import { NotificationsStart } from '@kbn/core/public';
|
||||
import { apiService } from '../services/api';
|
||||
import { apiService } from '../services/api.service';
|
||||
|
||||
interface QuitGuideModalProps {
|
||||
closeModal: () => void;
|
||||
|
|
|
@ -27,7 +27,7 @@ import type {
|
|||
GuidedOnboardingPluginStart,
|
||||
} from './types';
|
||||
import { GuidePanel } from './components';
|
||||
import { ApiService, apiService } from './services/api';
|
||||
import { ApiService, apiService } from './services/api.service';
|
||||
|
||||
export class GuidedOnboardingPlugin
|
||||
implements Plugin<GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart>
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
getStepConfig,
|
||||
isLastStep,
|
||||
} from './helpers';
|
||||
import { ConfigService } from './config_service';
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
export class ApiService implements GuidedOnboardingApi {
|
||||
private isCloudEnabled: boolean | undefined;
|
||||
|
@ -123,6 +123,10 @@ export class ApiService implements GuidedOnboardingApi {
|
|||
if (!this.client) {
|
||||
throw new Error('ApiService has not be initialized.');
|
||||
}
|
||||
// don't send a request if a request is already in flight
|
||||
if (this.isLoading$.value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isLoading$.next(true);
|
||||
|
@ -152,6 +156,10 @@ export class ApiService implements GuidedOnboardingApi {
|
|||
if (!this.client) {
|
||||
throw new Error('ApiService has not be initialized.');
|
||||
}
|
||||
// don't send a request if a request is already in flight
|
||||
if (this.isLoading$.value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isLoading$.next(true);
|
||||
|
@ -474,6 +482,10 @@ export class ApiService implements GuidedOnboardingApi {
|
|||
if (!this.client) {
|
||||
throw new Error('ApiService has not be initialized.');
|
||||
}
|
||||
// don't send a request if a request is already in flight
|
||||
if (this.isLoading$.value) {
|
||||
return undefined;
|
||||
}
|
||||
this.isLoading$.next(true);
|
||||
const config = await this.configService.getGuideConfig(guideId);
|
||||
this.isLoading$.next(false);
|
|
@ -13,7 +13,7 @@ import { testGuideConfig, testGuideId } from '@kbn/guided-onboarding';
|
|||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
|
||||
import { API_BASE_PATH } from '../../common';
|
||||
import { ApiService } from './api';
|
||||
import { ApiService } from './api.service';
|
||||
import {
|
||||
testGuideFirstStep,
|
||||
testGuideLastStep,
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
wrongIntegration,
|
||||
} from './api.mocks';
|
||||
|
||||
import { ConfigService } from './config_service';
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
describe('GuidedOnboarding ConfigService', () => {
|
||||
let configService: ConfigService;
|
|
@ -36,7 +36,7 @@ exports[`getting started should render getting started component 1`] = `
|
|||
textAlign="center"
|
||||
>
|
||||
<p>
|
||||
Select a guide to help you make the most of your data.
|
||||
Select an option and we'll help you get started.
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
|
@ -45,55 +45,19 @@ exports[`getting started should render getting started component 1`] = `
|
|||
<EuiSpacer
|
||||
size="xxl"
|
||||
/>
|
||||
<EuiFlexGrid
|
||||
columns={4}
|
||||
gutterSize="l"
|
||||
>
|
||||
<EuiFlexItem
|
||||
key="guideCard-search"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[Function]}
|
||||
addBasePath={[Function]}
|
||||
guides={Array []}
|
||||
isDarkTheme={false}
|
||||
useCase="search"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
key="guideCard-kubernetes"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[Function]}
|
||||
addBasePath={[Function]}
|
||||
guides={Array []}
|
||||
isDarkTheme={false}
|
||||
useCase="kubernetes"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
key="linkCard-infrastructure"
|
||||
>
|
||||
<InfrastructureLinkCard
|
||||
addBasePath={[Function]}
|
||||
isDarkTheme={false}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
key="guideCard-siem"
|
||||
>
|
||||
<GuideCard
|
||||
activateGuide={[Function]}
|
||||
addBasePath={[Function]}
|
||||
guides={Array []}
|
||||
isDarkTheme={false}
|
||||
useCase="siem"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer />
|
||||
<EuiHorizontalRule />
|
||||
<GuideFilters
|
||||
activeFilter="all"
|
||||
setActiveFilter={[Function]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xxl"
|
||||
/>
|
||||
<GuideCards
|
||||
activateGuide={[Function]}
|
||||
activeFilter="all"
|
||||
guidesState={Array []}
|
||||
navigateToApp={[MockFunction]}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<div
|
||||
className="eui-textCenter"
|
||||
|
@ -102,7 +66,7 @@ exports[`getting started should render getting started component 1`] = `
|
|||
data-test-subj="onboarding--skipGuideLink"
|
||||
onClick={[Function]}
|
||||
>
|
||||
I’d like to do something else (skip)
|
||||
I’d like to do something else.
|
||||
</EuiLink>
|
||||
</div>
|
||||
</_EuiPageSection>
|
||||
|
|
|
@ -12,8 +12,7 @@ import { act } from 'react-dom/test-utils';
|
|||
import { findTestSubject, registerTestBed, TestBed } from '@kbn/test-jest-helpers';
|
||||
import { cloudMock } from '@kbn/cloud-plugin/public/mocks';
|
||||
import { chromeServiceMock, applicationServiceMock, httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
|
||||
import { ApiService } from '@kbn/guided-onboarding-plugin/public/services/api';
|
||||
import { ApiService } from '@kbn/guided-onboarding-plugin/public/services/api.service';
|
||||
|
||||
import { GettingStarted } from './getting_started';
|
||||
import { KEY_ENABLE_WELCOME } from '../home';
|
||||
|
@ -21,8 +20,6 @@ import { KEY_ENABLE_WELCOME } from '../home';
|
|||
const mockCloud = cloudMock.createSetup();
|
||||
const mockChrome = chromeServiceMock.createStartContract();
|
||||
const mockApplication = applicationServiceMock.createStartContract();
|
||||
const mockSettingsUI = uiSettingsServiceMock.createSetupContract();
|
||||
mockSettingsUI.get.mockReturnValue(false);
|
||||
const mockHttp = httpServiceMock.createStartContract();
|
||||
const mockApiService = new ApiService();
|
||||
mockApiService.setup(mockHttp, true);
|
||||
|
@ -33,8 +30,6 @@ jest.mock('../../kibana_services', () => ({
|
|||
chrome: mockChrome,
|
||||
application: mockApplication,
|
||||
trackUiMetric: jest.fn(),
|
||||
uiSettings: mockSettingsUI,
|
||||
http: mockHttp,
|
||||
guidedOnboardingService: mockApiService,
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
EuiPageTemplate,
|
||||
|
@ -26,9 +23,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, GuideCardUseCase } from '@kbn/guided-onboarding';
|
||||
import { GuideCard, InfrastructureLinkCard } from '@kbn/guided-onboarding';
|
||||
import type { GuideFilterValues, GuideId, GuideState } from '@kbn/guided-onboarding';
|
||||
|
||||
import { GuideCards, GuideFilters } from '@kbn/guided-onboarding';
|
||||
import { getServices } from '../../kibana_services';
|
||||
import { KEY_ENABLE_WELCOME } from '../home';
|
||||
|
||||
|
@ -40,18 +37,18 @@ const title = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelect
|
|||
defaultMessage: 'What would you like to do first?',
|
||||
});
|
||||
const subtitle = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle', {
|
||||
defaultMessage: 'Select a guide to help you make the most of your data.',
|
||||
defaultMessage: `Select an option and we'll help you get started.`,
|
||||
});
|
||||
const skipText = i18n.translate('home.guidedOnboarding.gettingStarted.skip.buttonLabel', {
|
||||
defaultMessage: `I’d like to do something else (skip)`,
|
||||
defaultMessage: `I’d like to do something else.`,
|
||||
});
|
||||
|
||||
export const GettingStarted = () => {
|
||||
const { application, trackUiMetric, chrome, guidedOnboardingService, http, uiSettings, cloud } =
|
||||
getServices();
|
||||
const { application, trackUiMetric, chrome, guidedOnboardingService, cloud } = getServices();
|
||||
const [guidesState, setGuidesState] = useState<GuideState[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isError, setIsError] = useState<boolean>(false);
|
||||
const [filter, setFilter] = useState<GuideFilterValues>('all');
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -114,11 +111,10 @@ export const GettingStarted = () => {
|
|||
padding: calc(${euiTheme.size.base}*3) calc(${euiTheme.size.base}*4);
|
||||
`;
|
||||
|
||||
const isDarkTheme = uiSettings.get<boolean>('theme:darkMode');
|
||||
const activateGuide = useCallback(
|
||||
async (useCase: GuideCardUseCase, guideState?: GuideState) => {
|
||||
async (guideId: GuideId, guideState?: GuideState) => {
|
||||
try {
|
||||
await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState);
|
||||
await guidedOnboardingService?.activateGuide(guideId, guideState);
|
||||
} catch (err) {
|
||||
getServices().toastNotifications.addDanger({
|
||||
title: i18n.translate('home.guidedOnboarding.gettingStarted.activateGuide.errorMessage', {
|
||||
|
@ -200,34 +196,14 @@ export const GettingStarted = () => {
|
|||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiFlexGrid columns={4} gutterSize="l">
|
||||
{['search', 'kubernetes', 'infrastructure', 'siem'].map((useCase) => {
|
||||
if (useCase === 'infrastructure') {
|
||||
return (
|
||||
<EuiFlexItem key={`linkCard-${useCase}`}>
|
||||
<InfrastructureLinkCard
|
||||
navigateToApp={application.navigateToApp}
|
||||
isDarkTheme={isDarkTheme}
|
||||
addBasePath={http.basePath.prepend}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EuiFlexItem key={`guideCard-${useCase}`}>
|
||||
<GuideCard
|
||||
useCase={useCase as GuideCardUseCase}
|
||||
guides={guidesState}
|
||||
activateGuide={activateGuide}
|
||||
isDarkTheme={isDarkTheme}
|
||||
addBasePath={http.basePath.prepend}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer />
|
||||
<EuiHorizontalRule />
|
||||
<GuideFilters activeFilter={filter} setActiveFilter={setFilter} />
|
||||
<EuiSpacer size="xxl" />
|
||||
<GuideCards
|
||||
activateGuide={activateGuide}
|
||||
navigateToApp={application.navigateToApp}
|
||||
activeFilter={filter}
|
||||
guidesState={guidesState}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<div className="eui-textCenter">
|
||||
{/* data-test-subj used for FS tracking */}
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
"@kbn/shared-ux-page-kibana-template",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/guided-onboarding",
|
||||
"@kbn/core-ui-settings-browser-mocks",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/utility-types-jest",
|
||||
|
|
|
@ -3015,15 +3015,6 @@
|
|||
"guidedOnboarding.quitGuideModal.modalDescription": "Vous pouvez redémarrer le guide de configuration à tout moment à partir du menu Aide.",
|
||||
"guidedOnboarding.quitGuideModal.modalTitle": "Quitter ce guide ?",
|
||||
"guidedOnboarding.quitGuideModal.quitButtonLabel": "Quitter le guide",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress} étapes",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "Continuer",
|
||||
"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.startGuide.buttonLabel": "Afficher le guide",
|
||||
"guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "rechercher",
|
||||
"guidedOnboardingPackage.gettingStarted.search.iconName": "Logo Entreprise Search",
|
||||
"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",
|
||||
|
|
|
@ -3013,15 +3013,6 @@
|
|||
"guidedOnboarding.quitGuideModal.modalDescription": "[ヘルプ]メニューを使用すると、いつでもセットアップガイドを再開できます。",
|
||||
"guidedOnboarding.quitGuideModal.modalTitle": "このガイドを終了しますか?",
|
||||
"guidedOnboarding.quitGuideModal.quitButtonLabel": "ガイドを終了",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress}ステップ",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "続行",
|
||||
"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.startGuide.buttonLabel": "ガイドを表示",
|
||||
"guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "検索",
|
||||
"guidedOnboardingPackage.gettingStarted.search.iconName": "エンタープライズ サーチロゴ",
|
||||
"home.loadTutorials.requestFailedErrorMessage": "リクエスト失敗、ステータスコード:{status}",
|
||||
"home.tutorial.addDataToKibanaDescription": "{integrationsLink}を追加するほかに、サンプルデータを試したり、独自のデータをアップロードしたりできます。",
|
||||
"home.tutorial.noTutorialLabel": "チュートリアル {tutorialId} が見つかりません",
|
||||
|
|
|
@ -3017,15 +3017,6 @@
|
|||
"guidedOnboarding.quitGuideModal.modalDescription": "您可以随时从“帮助”菜单重新启动设置指南。",
|
||||
"guidedOnboarding.quitGuideModal.modalTitle": "退出本指南?",
|
||||
"guidedOnboarding.quitGuideModal.quitButtonLabel": "退出指南",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress} 步骤",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "继续",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "已完成",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "进行中",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "为您的网站、应用程序、工作区内容或期间的任何内容创建搜索体验。",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "搜索我的数据",
|
||||
"guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "查看指南",
|
||||
"guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "搜索",
|
||||
"guidedOnboardingPackage.gettingStarted.search.iconName": "Enterprise Search 徽标",
|
||||
"home.loadTutorials.requestFailedErrorMessage": "请求失败,状态代码:{status}",
|
||||
"home.tutorial.addDataToKibanaDescription": "除了添加 {integrationsLink} 以外,您还可以试用样例数据或上传自己的数据。",
|
||||
"home.tutorial.noTutorialLabel": "无法找到教程 {tutorialId}",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue