[Guided onboarding] Landing page updates (#143194)

* [Guided onboarding] Updated landing page

* [Guided onboarding] Finished landing page changes

* [Guided onboarding] Fixed card for completed guides

* [Guided onboarding] Fixed types errors

* [Guided onboarding] Fixed i18n issues

* Update src/plugins/home/public/application/components/guided_onboarding/use_case_card.tsx

Co-authored-by: Cindy Chang  <cindyisachang@gmail.com>

* Update src/plugins/home/public/application/components/guided_onboarding/use_case_card.tsx

Co-authored-by: Cindy Chang  <cindyisachang@gmail.com>

* Update src/plugins/home/public/application/components/guided_onboarding/use_case_card.tsx

Co-authored-by: Cindy Chang  <cindyisachang@gmail.com>

* [Guided onboarding] Added CR comments

* [Guided onboarding] Added view guide button to the completed guide

* [Guided onboarding] Fixed the typo in kibana services

* [Guided onboarding] Started moving the components out of home plugin into the guided onboarding package

* [Guided onboarding] Fix the imports in the plugin

* [Guided onboarding] Fix the tests in the new package

* [CI] Auto-commit changed files from 'node scripts/generate codeowners'

* [Guided onboarding] Fix the package file and the yarn.lock file

* [Guided onboarding] Fix the build

* [Guided onboarding] More refactoring

* [Guided onboarding] More refactoring

* [Guided onboarding] More refactoring

* [Guided onboarding] More refactoring of types

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

* [Guided onboarding] More refactoring of types

* [Guided onboarding] Fix the types issues

* [Guided onboarding] Update the tests for the api

* [Guided onboarding] Fixed the i18n errors

* [Guided onboarding] Fixed the i18n errors

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* [Guided onboarding] Fixed the jest tests

* [Guided onboarding] Home changes

* Update packages/kbn-guided-onboarding/src/components/landing_page/observability_link_card.tsx

Co-authored-by: Kelly Murphy <kelly.murphy@elastic.co>

* [Guided onboarding] Address copy feedback

* [Guided onboarding] Address CR feedback

Co-authored-by: Cindy Chang  <cindyisachang@gmail.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Kelly Murphy <kelly.murphy@elastic.co>
This commit is contained in:
Yulia Čech 2022-10-20 11:36:04 +02:00 committed by GitHub
parent 27f4f73623
commit 9c0dd18577
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1084 additions and 402 deletions

1
.github/CODEOWNERS vendored
View file

@ -905,6 +905,7 @@ packages/kbn-ftr-common-functional-services @elastic/kibana-operations
packages/kbn-ftr-screenshot-filename @elastic/kibana-operations
packages/kbn-generate @elastic/kibana-operations
packages/kbn-get-repo-files @elastic/kibana-operations
packages/kbn-guided-onboarding @elastic/platform-onboarding
packages/kbn-handlebars @elastic/kibana-security
packages/kbn-hapi-mocks @elastic/kibana-core
packages/kbn-i18n @elastic/kibana-core

View file

@ -43,6 +43,7 @@
"fieldFormats": "src/plugins/field_formats",
"flot": "packages/kbn-flot-charts/lib",
"guidedOnboarding": "src/plugins/guided_onboarding",
"guidedOnboardingPackage": "packages/kbn-guided-onboarding",
"home": "src/plugins/home",
"homePackages": "packages/home",
"indexPatternEditor": "src/plugins/data_view_editor",

View file

@ -25,13 +25,8 @@ import {
EuiText,
EuiTitle,
} from '@elastic/eui';
import type {
GuidedOnboardingPluginStart,
GuideState,
GuideStepIds,
GuideId,
GuideStep,
} from '@kbn/guided-onboarding-plugin/public';
import type { GuideState, GuideStepIds, GuideId, GuideStep } from '@kbn/guided-onboarding';
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { guidesConfig } from '@kbn/guided-onboarding-plugin/public';
interface MainProps {

View file

@ -6,7 +6,6 @@
* Side Public License, v 1.
*/
import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
@ -16,6 +15,5 @@ export interface GuidedOnboardingExamplePluginSetup {}
export interface GuidedOnboardingExamplePluginStart {}
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
guidedOnboarding: GuidedOnboardingPluginStart;
}

View file

@ -323,6 +323,7 @@
"@kbn/es-types": "link:bazel-bin/packages/kbn-es-types",
"@kbn/field-types": "link:bazel-bin/packages/kbn-field-types",
"@kbn/flot-charts": "link:bazel-bin/packages/kbn-flot-charts",
"@kbn/guided-onboarding": "link:bazel-bin/packages/kbn-guided-onboarding",
"@kbn/handlebars": "link:bazel-bin/packages/kbn-handlebars",
"@kbn/hapi-mocks": "link:bazel-bin/packages/kbn-hapi-mocks",
"@kbn/home-sample-data-card": "link:bazel-bin/packages/home/sample_data_card",
@ -1075,6 +1076,7 @@
"@types/kbn__ftr-screenshot-filename": "link:bazel-bin/packages/kbn-ftr-screenshot-filename/npm_module_types",
"@types/kbn__generate": "link:bazel-bin/packages/kbn-generate/npm_module_types",
"@types/kbn__get-repo-files": "link:bazel-bin/packages/kbn-get-repo-files/npm_module_types",
"@types/kbn__guided-onboarding": "link:bazel-bin/packages/kbn-guided-onboarding/npm_module_types",
"@types/kbn__handlebars": "link:bazel-bin/packages/kbn-handlebars/npm_module_types",
"@types/kbn__hapi-mocks": "link:bazel-bin/packages/kbn-hapi-mocks/npm_module_types",
"@types/kbn__home-sample-data-card": "link:bazel-bin/packages/home/sample_data_card/npm_module_types",

View file

@ -231,6 +231,7 @@ filegroup(
"//packages/kbn-ftr-screenshot-filename:build",
"//packages/kbn-generate:build",
"//packages/kbn-get-repo-files:build",
"//packages/kbn-guided-onboarding:build",
"//packages/kbn-handlebars:build",
"//packages/kbn-hapi-mocks:build",
"//packages/kbn-i18n:build",
@ -569,6 +570,7 @@ filegroup(
"//packages/kbn-ftr-screenshot-filename:build_types",
"//packages/kbn-generate:build_types",
"//packages/kbn-get-repo-files:build_types",
"//packages/kbn-guided-onboarding:build_types",
"//packages/kbn-handlebars:build_types",
"//packages/kbn-hapi-mocks:build_types",
"//packages/kbn-i18n:build_types",

View file

@ -0,0 +1,152 @@
load("@npm//@bazel/typescript:index.bzl", "ts_config")
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project")
PKG_DIRNAME = "kbn-guided-onboarding"
PKG_REQUIRE_NAME = "@kbn/guided-onboarding"
SOURCE_FILES = glob(
[
"**/*.ts",
"**/*.tsx",
],
exclude = [
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)
SRCS = SOURCE_FILES
filegroup(
name = "srcs",
srcs = SRCS,
)
NPM_MODULE_EXTRA_FILES = [
"package.json",
]
# In this array place runtime dependencies, including other packages and NPM packages
# which must be available for this code to run.
#
# To reference other packages use:
# "//repo/relative/path/to/package"
# eg. "//packages/kbn-utils"
#
# To reference a NPM package use:
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
"@npm//@elastic/eui",
"@npm//enzyme",
"@npm//react",
"//packages/kbn-i18n-react",
"//packages/kbn-i18n",
"//packages/core/http/core-http-browser",
"//packages/core/ui-settings/core-ui-settings-browser",
]
# In this array place dependencies necessary to build the types, which will include the
# :npm_module_types target of other packages and packages from NPM, including @types/*
# packages.
#
# To reference the types for another package use:
# "//repo/relative/path/to/package:npm_module_types"
# eg. "//packages/kbn-utils:npm_module_types"
#
# References to NPM packages work the same as RUNTIME_DEPS
TYPES_DEPS = [
"@npm//@types/node",
"@npm//@types/jest",
"@npm//@elastic/eui",
"@npm//@types/enzyme",
"@npm//@types/react",
"//packages/kbn-i18n-react:npm_module_types",
"//packages/kbn-i18n:npm_module_types",
"//packages/core/http/core-http-browser:npm_module_types",
"//packages/core/ui-settings/core-ui-settings-browser:npm_module_types",
"//packages/core/application/core-application-browser:npm_module_types",
]
jsts_transpiler(
name = "target_node",
srcs = SRCS,
build_pkg_name = package_name(),
)
jsts_transpiler(
name = "target_web",
srcs = SRCS,
build_pkg_name = package_name(),
web = True,
additional_args = [
"--copy-files",
"--quiet"
],
)
ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//:tsconfig.base.json",
"//:tsconfig.bazel.json",
],
)
ts_project(
name = "tsc_types",
args = ['--pretty'],
srcs = SRCS,
deps = TYPES_DEPS,
declaration = True,
declaration_map = True,
emit_declaration_only = True,
out_dir = "target_types",
tsconfig = ":tsconfig",
)
js_library(
name = PKG_DIRNAME,
srcs = NPM_MODULE_EXTRA_FILES,
deps = RUNTIME_DEPS + [":target_node", ":target_web"],
package_name = PKG_REQUIRE_NAME,
visibility = ["//visibility:public"],
)
pkg_npm(
name = "npm_module",
deps = [":" + PKG_DIRNAME],
)
filegroup(
name = "build",
srcs = [":npm_module"],
visibility = ["//visibility:public"],
)
pkg_npm_types(
name = "npm_module_types",
srcs = SRCS,
deps = [":tsc_types"],
package_name = PKG_REQUIRE_NAME,
tsconfig = ":tsconfig",
visibility = ["//visibility:public"],
)
filegroup(
name = "build_types",
srcs = [":npm_module_types"],
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,3 @@
# @kbn/guided-onboarding
Empty package generated by @kbn/generate

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export type { GuideState, GuideId } from './src/types';
export { GuideCard, ObservabilityLinkCard } from './src/components/landing_page';
export type { UseCase } from './src/components/landing_page';

View file

@ -0,0 +1,13 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-guided-onboarding'],
};

View file

@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/guided-onboarding",
"owner": "@elastic/platform-onboarding",
"runtimeDeps": [],
"typeDeps": [],
}

View file

@ -0,0 +1,8 @@
{
"name": "@kbn/guided-onboarding",
"private": true,
"version": "1.0.0",
"main": "./target_node/index.js",
"browser": "./target_web/index.js",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -0,0 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`guide card snapshots should render use case card component for observability 1`] = `
<UseCaseCard
addBasePath={[MockFunction]}
description="Monitor your Kubernetes infrastructure by consolidating your logs and metrics."
footer={
<GuideCardFooter
activateGuide={[MockFunction]}
guides={Array []}
useCase="observability"
/>
}
isDarkTheme={false}
title="Observe my Kubernetes infrastructure"
useCase="observability"
/>
`;
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 []}
useCase="search"
/>
}
isDarkTheme={false}
title="Search my data"
useCase="search"
/>
`;
exports[`guide card snapshots should render use case card component for security 1`] = `
<UseCaseCard
addBasePath={[MockFunction]}
description="Defend your environment against threats by unifying SIEM, endpoint security, and cloud security."
footer={
<GuideCardFooter
activateGuide={[MockFunction]}
guides={Array []}
useCase="security"
/>
}
isDarkTheme={false}
title="Protect my environment"
useCase="security"
/>
`;

View file

@ -0,0 +1,107 @@
// 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"
max={2}
size="s"
value={1}
valueText="1/2 steps"
/>
<EuiSpacer
size="l"
/>
<div
className="eui-textCenter"
>
<EuiButton
data-test-subj="onboarding--guideCard--view--search"
fill={true}
onClick={[Function]}
>
View guide
</EuiButton>
</div>
</Fragment>
`;
exports[`guide card footer snapshots should render the footer when the guide has not started yet 1`] = `
<div
className="eui-textCenter"
>
<EuiButton
data-test-subj="onboarding--guideCard--view--search"
fill={true}
onClick={[Function]}
>
View guide
</EuiButton>
</div>
`;
exports[`guide card footer snapshots should render the footer when the guide is in progress 1`] = `
<Fragment>
<EuiProgress
label="In progress"
max={2}
size="s"
value={1}
valueText="1/2 steps"
/>
<EuiSpacer
size="l"
/>
<div
className="eui-textCenter"
>
<EuiButton
data-test-subj="onboarding--guideCard--continue--search"
fill={true}
onClick={[Function]}
>
Continue
</EuiButton>
</div>
</Fragment>
`;
exports[`guide card footer snapshots should render the footer when the guide is ready to complete 1`] = `
<Fragment>
<EuiProgress
label="In progress"
max={2}
size="s"
value={1}
valueText="1/2 steps"
/>
<EuiSpacer
size="l"
/>
<div
className="eui-textCenter"
>
<EuiButton
data-test-subj="onboarding--guideCard--continue--search"
fill={true}
onClick={[Function]}
>
Continue
</EuiButton>
</div>
</Fragment>
`;
exports[`guide card footer snapshots should render the footer when the guided onboarding has not started yet 1`] = `
<div
className="eui-textCenter"
>
<EuiButton
data-test-subj="onboarding--guideCard--view--search"
fill={true}
onClick={[Function]}
>
View guide
</EuiButton>
</div>
`;

View file

@ -0,0 +1,28 @@
// 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
data-test-subj="onboarding--linkCard--observability"
fill={true}
onClick={[Function]}
>
View integrations
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
}
isDarkTheme={false}
title="Observe my data"
useCase="observability"
/>
`;

View file

@ -0,0 +1,40 @@
/*
* 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 observability', async () => {
const component = await shallow(<GuideCard {...defaultProps} useCase="observability" />);
expect(component).toMatchSnapshot();
});
test('should render use case card component for security', async () => {
const component = await shallow(<GuideCard {...defaultProps} useCase="security" />);
expect(component).toMatchSnapshot();
});
});
});

View file

@ -0,0 +1,96 @@
/*
* 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 { GuideState } from '../../types';
import { GuideCardFooter } from './guide_card_footer';
import { UseCase, UseCaseCard } from './use_case_card';
type GuideCardConstants = {
[key in UseCase]: {
i18nTexts: {
title: string;
description: string;
};
};
};
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.',
}
),
},
},
observability: {
i18nTexts: {
title: i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.observability.cardTitle',
{
defaultMessage: 'Observe my Kubernetes infrastructure',
}
),
description: i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.observability.cardDescription',
{
defaultMessage:
'Monitor your Kubernetes infrastructure by consolidating your logs and metrics.',
}
),
},
},
security: {
i18nTexts: {
title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.security.cardTitle', {
defaultMessage: 'Protect my environment',
}),
description: i18n.translate(
'guidedOnboardingPackage.gettingStarted.guideCard.security.cardDescription',
{
defaultMessage:
'Defend your environment against threats by unifying SIEM, endpoint security, and cloud security.',
}
),
},
},
};
export interface GuideCardProps {
useCase: UseCase;
guides: GuideState[];
activateGuide: (useCase: UseCase, guide?: GuideState) => Promise<void>;
isDarkTheme: boolean;
addBasePath: (url: string) => string;
}
export const GuideCard = ({
useCase,
guides,
activateGuide,
isDarkTheme,
addBasePath,
}: GuideCardProps) => {
return (
<UseCaseCard
useCase={useCase}
title={constants[useCase].i18nTexts.title}
description={constants[useCase].i18nTexts.description}
footer={<GuideCardFooter guides={guides} activateGuide={activateGuide} useCase={useCase} />}
isDarkTheme={isDarkTheme}
addBasePath={addBasePath}
/>
);
};

View file

@ -0,0 +1,71 @@
/*
* 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',
activateGuide: jest.fn(),
};
const searchGuideState: GuideState = {
guideId: 'search',
status: 'not_started',
steps: [
{ id: 'add_data', status: 'complete' },
{ id: 'browse_docs', 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();
});
});
});

View file

@ -0,0 +1,114 @@
/*
* 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, EuiProgress, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { GuideId, GuideState } from '../../types';
import { UseCase } from './use_case_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',
}
);
export interface GuideCardFooterProps {
guides: GuideState[];
useCase: UseCase;
activateGuide: (useCase: UseCase, guideState?: GuideState) => void;
}
export const GuideCardFooter = ({ guides, useCase, activateGuide }: GuideCardFooterProps) => {
const guideState = guides.find((guide) => guide.guideId === (useCase as GuideId));
const viewGuideButton = (
<div className="eui-textCenter">
<EuiButton
// Used for FS tracking
data-test-subj={`onboarding--guideCard--view--${useCase}`}
fill
onClick={() => activateGuide(useCase, guideState)}
>
{viewGuideLabel}
</EuiButton>
</div>
);
// 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}
/>
<EuiSpacer size="l" />
{viewGuideButton}
</>
);
}
// guide is in progress or ready to complete
return (
<>
<EuiProgress
valueText={stepsLabel}
value={numberCompleteSteps}
max={numberSteps}
size="s"
label={inProgressLabel}
/>
<EuiSpacer size="l" />
<div className="eui-textCenter">
<EuiButton
// Used for FS tracking
data-test-subj={`onboarding--guideCard--continue--${useCase}`}
fill
onClick={() => activateGuide(useCase, guideState)}
>
{continueGuideLabel}
</EuiButton>
</div>
</>
);
};

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export { GuideCard } from './guide_card';
export { ObservabilityLinkCard } from './observability_link_card';
export type { UseCase } from './use_case_card';

View file

@ -0,0 +1,26 @@
/*
* 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 { ObservabilityLinkCard } from './observability_link_card';
const defaultProps = {
navigateToApp: jest.fn(),
isDarkTheme: false,
addBasePath: jest.fn(),
};
describe('observability link card', () => {
describe('snapshots', () => {
test('should render link card for observability', async () => {
const component = await shallow(<ObservabilityLinkCard {...defaultProps} />);
expect(component).toMatchSnapshot();
});
});
});

View file

@ -0,0 +1,84 @@
/*
* 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 {
observability: {
i18nTexts: {
title: string;
description: string;
};
};
}
const constants: LinkCardConstants = {
observability: {
i18nTexts: {
title: i18n.translate(
'guidedOnboardingPackage.gettingStarted.linkCard.observability.cardTitle',
{
defaultMessage: 'Observe my data',
}
),
description: i18n.translate(
'guidedOnboardingPackage.gettingStarted.linkCard.observability.cardDescription',
{
defaultMessage:
'Add application, infrastructure, and user data through our pre-built integrations.',
}
),
},
},
};
export const ObservabilityLinkCard = ({
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.linkCard.buttonLabel', {
defaultMessage: 'View integrations',
})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
);
return (
<UseCaseCard
useCase={'observability'}
title={constants.observability.i18nTexts.title}
description={constants.observability.i18nTexts.description}
footer={button}
isDarkTheme={isDarkTheme}
addBasePath={addBasePath}
/>
);
};

View file

@ -0,0 +1,105 @@
/*
* 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 { GuideId } from '../../types';
type UseCaseConstants = {
[key in UseCase]: {
logAltText: string;
betaBadgeLabel: 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',
}),
},
observability: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.observability.iconName', {
defaultMessage: 'Observability logo',
}),
betaBadgeLabel: i18n.translate(
'guidedOnboardingPackage.gettingStarted.observability.betaBadgeLabel',
{
defaultMessage: 'observe',
}
),
},
security: {
logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.security.iconName', {
defaultMessage: 'Security logo',
}),
betaBadgeLabel: i18n.translate(
'guidedOnboardingPackage.gettingStarted.security.betaBadgeLabel',
{
defaultMessage: 'security',
}
),
},
};
export type UseCase = 'search' | 'observability' | 'security';
export interface UseCaseCardProps {
useCase: GuideId;
title: string;
description: string;
footer: ReactNode;
isDarkTheme: boolean;
addBasePath: (url: string) => string;
}
export const UseCaseCard = ({
useCase,
title,
description,
footer,
isDarkTheme,
addBasePath,
}: UseCaseCardProps) => {
const getImageUrl = (imageName: UseCase) => {
const imagePath = `/plugins/home/assets/solution_logos/${imageName}${
isDarkTheme ? '_dark' : ''
}.png`;
return addBasePath(imagePath);
};
const titleElement = (
<EuiText textAlign="center">
<h4>
<strong>{title}</strong>
</h4>
</EuiText>
);
const descriptionElement = (
<EuiText size="s" textAlign="center">
<p>{description}</p>
</EuiText>
);
return (
<EuiCard
display="subdued"
textAlign="left"
image={<EuiImage src={getImageUrl(useCase)} alt={constants[useCase].logAltText} />}
title={titleElement}
description={descriptionElement}
footer={footer}
betaBadgeProps={{
label: constants[useCase].betaBadgeLabel,
}}
/>
);
};

View file

@ -14,13 +14,21 @@ export type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience';
export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds;
export interface GuideState {
guideId: GuideId;
status: GuideStatus;
isActive?: boolean; // Drives the current guide shown in the dropdown panel
steps: GuideStep[];
}
/**
* Allowed states for a guide:
* in_progress: Guide has been started
* not_started: Guide has not been started
* in_progress: At least one step in the guide has been started
* ready_to_complete: All steps have been completed, but the "Continue using Elastic" button has not been clicked
* complete: All steps and the guide have been completed
*/
export type GuideStatus = 'in_progress' | 'ready_to_complete' | 'complete';
export type GuideStatus = 'not_started' | 'in_progress' | 'ready_to_complete' | 'complete';
/**
* Allowed states for each step in a guide:
@ -36,10 +44,3 @@ export interface GuideStep {
id: GuideStepIds;
status: StepStatus;
}
export interface GuideState {
guideId: GuideId;
status: GuideStatus;
isActive?: boolean; // Drives the current guide shown in the dropdown panel
steps: GuideStep[];
}

View file

@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.bazel.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "target_types",
"stripInternal": false,
"types": [
"jest",
"node",
]
},
"include": [
"**/*.ts",
"**/*.tsx"
]
}

View file

@ -9,7 +9,8 @@
import React from 'react';
import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { GuideState } from '../../common/types';
import type { GuideState } from '@kbn/guided-onboarding';
import { getStepConfig } from '../services/helpers';
import { GuideButtonPopover } from './guide_button_popover';

View file

@ -12,9 +12,9 @@ import React from 'react';
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
import { httpServiceMock } from '@kbn/core/public/mocks';
import { HttpSetup } from '@kbn/core/public';
import type { GuideState } from '@kbn/guided-onboarding';
import { guidesConfig } from '../constants/guides_config';
import type { GuideState } from '../../common/types';
import { apiService } from '../services/api';
import { GuidePanel } from './guide_panel';
import { registerTestBed, TestBed } from '@kbn/test-jest-helpers';

View file

@ -30,8 +30,8 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ApplicationStart } from '@kbn/core/public';
import type { GuideState, GuideStep as GuideStepStatus } from '@kbn/guided-onboarding';
import type { GuideState, GuideStep as GuideStepStatus } from '../../common/types';
import type { GuideConfig, StepConfig } from '../types';
import type { ApiService } from '../services/api';

View file

@ -8,7 +8,7 @@
import { EuiThemeComputed } from '@elastic/eui';
import { css } from '@emotion/react';
import { StepStatus } from '../../common/types';
import type { StepStatus } from '@kbn/guided-onboarding';
export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed, stepStatus: StepStatus) => ({
stepNumber: css`

View file

@ -21,7 +21,7 @@ import {
import { i18n } from '@kbn/i18n';
import type { StepStatus } from '../../common/types';
import type { StepStatus } from '@kbn/guided-onboarding';
import type { StepConfig } from '../types';
import { getGuidePanelStepStyles } from './guide_panel_step.styles';

View file

@ -9,7 +9,7 @@ import React, { useState } from 'react';
import { EuiText, EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { GuideState } from '../../common/types';
import type { GuideState } from '@kbn/guided-onboarding';
import { apiService } from '../services/api';
interface QuitGuideModalProps {

View file

@ -12,8 +12,10 @@ import { GuidedOnboardingPlugin } from './plugin';
export function plugin(ctx: PluginInitializerContext) {
return new GuidedOnboardingPlugin(ctx);
}
export type { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types';
export type { GuideId, GuideStepIds, GuideState, GuideStep } from '../common/types';
export type {
GuidedOnboardingPluginSetup,
GuidedOnboardingPluginStart,
GuidedOnboardingApi,
} from './types';
export { guidesConfig } from './constants/guides_config';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { GuideState } from '../../common/types';
import type { GuideState } from '@kbn/guided-onboarding';
export const searchAddDataActiveState: GuideState = {
guideId: 'search',

View file

@ -8,11 +8,11 @@
import { HttpSetup } from '@kbn/core/public';
import { httpServiceMock } from '@kbn/core/public/mocks';
import type { GuideState } from '@kbn/guided-onboarding';
import { firstValueFrom, Subscription } from 'rxjs';
import { API_BASE_PATH } from '../../common/constants';
import { guidesConfig } from '../constants/guides_config';
import type { GuideState } from '../../common/types';
import { ApiService } from './api';
import {
noGuideActiveState,
@ -57,7 +57,7 @@ describe('GuidedOnboarding ApiService', () => {
});
it('broadcasts the updated state', async () => {
await apiService.activateGuide(searchGuide);
await apiService.activateGuide(searchGuide, searchAddDataActiveState);
const state = await firstValueFrom(apiService.fetchActiveGuideState$());
expect(state).toEqual(searchAddDataActiveState);
@ -151,7 +151,7 @@ describe('GuidedOnboarding ApiService', () => {
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
body: JSON.stringify({
isActive: true,
status: 'in_progress',
status: 'not_started',
steps: [
{
id: 'add_data',

View file

@ -8,6 +8,7 @@
import { HttpSetup } from '@kbn/core/public';
import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs';
import type { GuideState, GuideId, GuideStep, GuideStepIds } from '@kbn/guided-onboarding';
import { GuidedOnboardingApi } from '../types';
import {
@ -21,7 +22,6 @@ import {
isStepReadyToComplete,
} from './helpers';
import { API_BASE_PATH } from '../../common/constants';
import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types';
export class ApiService implements GuidedOnboardingApi {
private client: HttpSetup | undefined;
@ -148,7 +148,7 @@ export class ApiService implements GuidedOnboardingApi {
const updatedGuide: GuideState = {
isActive: true,
status: 'in_progress',
status: 'not_started',
steps: updatedSteps,
guideId,
};

View file

@ -6,10 +6,9 @@
* Side Public License, v 1.
*/
import type { GuideId, GuideState, GuideStepIds } from '../../common/types';
import type { GuideId, GuideStepIds, GuideState, GuideStep } from '@kbn/guided-onboarding';
import { guidesConfig } from '../constants/guides_config';
import { GuideConfig, StepConfig } from '../types';
import { GuideStep } from '../../common/types';
export const getGuideConfig = (guideId?: GuideId): GuideConfig | undefined => {
if (guideId && Object.keys(guidesConfig).includes(guideId)) {

View file

@ -7,9 +7,8 @@
*/
import { Observable } from 'rxjs';
import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { HttpSetup } from '@kbn/core/public';
import { GuideId, GuideState, GuideStepIds, StepStatus } from '../common/types';
import type { GuideState, GuideId, GuideStepIds, StepStatus } from '@kbn/guided-onboarding';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface GuidedOnboardingPluginSetup {}
@ -18,10 +17,6 @@ export interface GuidedOnboardingPluginStart {
guidedOnboardingApi?: GuidedOnboardingApi;
}
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
}
export interface ClientConfigType {
ui: boolean;
}

View file

@ -8,8 +8,8 @@
import { schema } from '@kbn/config-schema';
import type { IRouter, SavedObjectsClient } from '@kbn/core/server';
import type { GuideState } from '@kbn/guided-onboarding';
import { API_BASE_PATH } from '../../common/constants';
import type { GuideState } from '../../common/types';
import { guidedSetupSavedObjectsType } from '../saved_objects';
const findGuideById = async (savedObjectsClient: SavedObjectsClient, guideId: string) => {

View file

@ -12,9 +12,6 @@
{
"path": "../../core/tsconfig.json"
},
{
"path": "../navigation/tsconfig.json"
},
{
"path": "../kibana_react/tsconfig.json"
},

View file

@ -8,6 +8,6 @@
"server": true,
"ui": true,
"requiredPlugins": ["dataViews", "share", "urlForwarding"],
"optionalPlugins": ["usageCollection", "customIntegrations", "cloud"],
"optionalPlugins": ["usageCollection", "customIntegrations", "cloud", "guidedOnboarding"],
"requiredBundles": ["kibanaReact"]
}

View file

@ -35,12 +35,11 @@ exports[`getting started should render getting started component 1`] = `
size="s"
/>
<EuiText
color="subdued"
size="s"
size="m"
textAlign="center"
>
<p>
Select a starting point for a quick tour of how Elastic can help you do even more with your data.
Select a guide to help you make the most of your data.
</p>
</EuiText>
<EuiSpacer
@ -50,21 +49,40 @@ exports[`getting started should render getting started component 1`] = `
size="xxl"
/>
<EuiFlexGrid
columns={3}
gutterSize="xl"
columns={4}
gutterSize="l"
>
<EuiFlexItem>
<UseCaseCard
<GuideCard
activateGuide={[Function]}
addBasePath={[MockFunction]}
guides={Array []}
isDarkTheme={false}
useCase="search"
/>
</EuiFlexItem>
<EuiFlexItem>
<UseCaseCard
<GuideCard
activateGuide={[Function]}
addBasePath={[MockFunction]}
guides={Array []}
isDarkTheme={false}
useCase="observability"
/>
</EuiFlexItem>
<EuiFlexItem>
<UseCaseCard
<ObservabilityLinkCard
addBasePath={[MockFunction]}
isDarkTheme={false}
navigateToApp={[MockFunction]}
/>
</EuiFlexItem>
<EuiFlexItem>
<GuideCard
activateGuide={[Function]}
addBasePath={[MockFunction]}
guides={Array []}
isDarkTheme={false}
useCase="security"
/>
</EuiFlexItem>
@ -79,7 +97,7 @@ exports[`getting started should render getting started component 1`] = `
data-test-subj="onboarding--skipUseCaseTourLink"
onClick={[Function]}
>
No thanks, Ill explore on my own.
Id like to do something else (skip)
</EuiLink>
</div>
</EuiPanel>

View file

@ -1,109 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`use case card should render use case card component for observability 1`] = `
<EuiCard
data-test-subj="onboarding--observabilityUseCaseCard"
description={
<EuiText
color="subdued"
size="xs"
>
<p>
Get end-to-end observability into your environments by consolidating your logs, metrics, and traces.
</p>
</EuiText>
}
display="subdued"
image={
<EuiImage
alt="Observability logo"
src="/plugins/home/assets/solution_logos/observability.png"
/>
}
onClick={[Function]}
textAlign="left"
title={
<EuiTitle
size="xs"
>
<h4>
<strong>
Monitor my environments
</strong>
</h4>
</EuiTitle>
}
/>
`;
exports[`use case card should render use case card component for search 1`] = `
<EuiCard
data-test-subj="onboarding--searchUseCaseCard"
description={
<EuiText
color="subdued"
size="xs"
>
<p>
Create a finely-tuned search experience for your websites, applications, workplace content, and more.
</p>
</EuiText>
}
display="subdued"
image={
<EuiImage
alt="Enterprise Search logo"
src="/plugins/home/assets/solution_logos/search.png"
/>
}
onClick={[Function]}
textAlign="left"
title={
<EuiTitle
size="xs"
>
<h4>
<strong>
Search my data
</strong>
</h4>
</EuiTitle>
}
/>
`;
exports[`use case card should render use case card component for security 1`] = `
<EuiCard
data-test-subj="onboarding--securityUseCaseCard"
description={
<EuiText
color="subdued"
size="xs"
>
<p>
Protect your environment against threats by unifying SIEM, endpoint security, and cloud security in one place.
</p>
</EuiText>
}
display="subdued"
image={
<EuiImage
alt="Security logo"
src="/plugins/home/assets/solution_logos/security.png"
/>
}
onClick={[Function]}
textAlign="left"
title={
<EuiTitle
size="xs"
>
<h4>
<strong>
Protect my environment
</strong>
</h4>
</EuiTitle>
}
/>
`;

View file

@ -31,6 +31,9 @@ jest.mock('../../kibana_services', () => {
prepend: jest.fn(),
},
},
guidedOnboardingService: {
fetchAllGuidesState: jest.fn(),
},
}),
};
});

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import React, { useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import {
EuiFlexGrid,
EuiFlexItem,
@ -24,28 +24,30 @@ import { css } from '@emotion/react';
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 { getServices } from '../../kibana_services';
import { KEY_ENABLE_WELCOME } from '../home';
import { UseCaseCard } from './use_case_card';
const homeBreadcrumb = i18n.translate('home.breadcrumbs.homeTitle', { defaultMessage: 'Home' });
const gettingStartedBreadcrumb = i18n.translate('home.breadcrumbs.gettingStartedTitle', {
defaultMessage: 'Getting Started',
defaultMessage: 'Guided setup',
});
const title = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionTitle', {
defaultMessage: 'What would you like to do first?',
});
const subtitle = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle', {
defaultMessage:
'Select a starting point for a quick tour of how Elastic can help you do even more with your data.',
defaultMessage: 'Select a guide to help you make the most of your data.',
});
const skipText = i18n.translate('home.guidedOnboarding.gettingStarted.skip.buttonLabel', {
defaultMessage: `No thanks, Ill explore on my own.`,
defaultMessage: `Id like to do something else (skip)`,
});
export const GettingStarted = () => {
const { application, trackUiMetric, chrome } = getServices();
const { application, trackUiMetric, chrome, guidedOnboardingService, http, uiSettings } =
getServices();
const [guidesState, setGuidesState] = useState<GuideState[]>([]);
useEffect(() => {
chrome.setBreadcrumbs([
@ -63,6 +65,17 @@ export const GettingStarted = () => {
]);
}, [chrome, trackUiMetric]);
const fetchGuidesState = useCallback(async () => {
const allGuides = await guidedOnboardingService?.fetchAllGuidesState();
if (allGuides) {
setGuidesState(allGuides.state);
}
}, [guidedOnboardingService]);
useEffect(() => {
fetchGuidesState();
}, [fetchGuidesState]);
const onSkip = () => {
trackUiMetric(METRIC_TYPE.CLICK, 'guided_onboarding__skipped');
// disable welcome screen on the home page
@ -73,6 +86,12 @@ export const GettingStarted = () => {
const paddingCss = css`
padding: calc(${euiTheme.size.base}*3) calc(${euiTheme.size.base}*4);
`;
const isDarkTheme = uiSettings.get<boolean>('theme:darkMode');
const activateGuide = async (useCase: UseCase, guideState?: GuideState) => {
await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState);
// TODO error handling https://github.com/elastic/kibana/issues/139798
};
return (
<KibanaPageTemplate panelled={false} grow>
<EuiPageTemplate.Section alignment="center">
@ -81,21 +100,36 @@ export const GettingStarted = () => {
<h1>{title}</h1>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText color="subdued" size="s" textAlign="center">
<EuiText size="m" textAlign="center">
<p>{subtitle}</p>
</EuiText>
<EuiSpacer size="s" />
<EuiSpacer size="xxl" />
<EuiFlexGrid columns={3} gutterSize="xl">
<EuiFlexItem>
<UseCaseCard useCase="search" />
</EuiFlexItem>
<EuiFlexItem>
<UseCaseCard useCase="observability" />
</EuiFlexItem>
<EuiFlexItem>
<UseCaseCard useCase="security" />
</EuiFlexItem>
<EuiFlexGrid columns={4} gutterSize="l">
{['search', 'observability', 'observabilityLink', 'security'].map((useCase) => {
if (useCase === 'observabilityLink') {
return (
<EuiFlexItem>
<ObservabilityLinkCard
navigateToApp={application.navigateToApp}
isDarkTheme={isDarkTheme}
addBasePath={http.basePath.prepend}
/>
</EuiFlexItem>
);
}
return (
<EuiFlexItem>
<GuideCard
useCase={useCase as UseCase}
guides={guidesState}
activateGuide={activateGuide}
isDarkTheme={isDarkTheme}
addBasePath={http.basePath.prepend}
/>
</EuiFlexItem>
);
})}
</EuiFlexGrid>
<EuiSpacer />
<EuiHorizontalRule />

View file

@ -1,42 +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 { UseCaseCard } from './use_case_card';
jest.mock('../../kibana_services', () => {
const { applicationServiceMock, uiSettingsServiceMock, httpServiceMock } =
jest.requireActual('@kbn/core/public/mocks');
return {
getServices: () => ({
application: applicationServiceMock.createStartContract(),
trackUiMetric: jest.fn(),
uiSettings: uiSettingsServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
}),
};
});
describe('use case card', () => {
test('should render use case card component for search', async () => {
const component = await shallow(<UseCaseCard useCase="search" />);
expect(component).toMatchSnapshot();
});
test('should render use case card component for observability', async () => {
const component = await shallow(<UseCaseCard useCase="observability" />);
expect(component).toMatchSnapshot();
});
test('should render use case card component for security', async () => {
const component = await shallow(<UseCaseCard useCase="security" />);
expect(component).toMatchSnapshot();
});
});

View file

@ -1,149 +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 { EuiCard, EuiText, EuiTitle, EuiImage } from '@elastic/eui';
import { METRIC_TYPE } from '@kbn/analytics';
import { i18n } from '@kbn/i18n';
import { getServices } from '../../kibana_services';
type UseCaseConstants = {
[key in UseCase]: {
i18nTexts: {
title: string;
description: string;
};
logo: {
altText: string;
};
navigateOptions: {
appId: string;
path?: string;
};
};
};
const constants: UseCaseConstants = {
search: {
i18nTexts: {
title: i18n.translate('home.guidedOnboarding.gettingStarted.search.cardTitle', {
defaultMessage: 'Search my data',
}),
description: i18n.translate('home.guidedOnboarding.gettingStarted.search.cardDescription', {
defaultMessage:
'Create a finely-tuned search experience for your websites, applications, workplace content, and more.',
}),
},
logo: {
altText: i18n.translate('home.guidedOnboarding.gettingStarted.search.iconName', {
defaultMessage: 'Enterprise Search logo',
}),
},
navigateOptions: {
appId: 'enterpriseSearch',
// when navigating to ent search, do not provide path
},
},
observability: {
i18nTexts: {
title: i18n.translate('home.guidedOnboarding.gettingStarted.observability.cardTitle', {
defaultMessage: 'Monitor my environments',
}),
description: i18n.translate(
'home.guidedOnboarding.gettingStarted.observability.cardDescription',
{
defaultMessage:
'Get end-to-end observability into your environments by consolidating your logs, metrics, and traces.',
}
),
},
logo: {
altText: i18n.translate('home.guidedOnboarding.gettingStarted.observability.iconName', {
defaultMessage: 'Observability logo',
}),
},
navigateOptions: {
appId: 'observability',
path: '/overview',
},
},
security: {
i18nTexts: {
title: i18n.translate('home.guidedOnboarding.gettingStarted.security.cardTitle', {
defaultMessage: 'Protect my environment',
}),
description: i18n.translate('home.guidedOnboarding.gettingStarted.security.cardDescription', {
defaultMessage:
'Protect your environment against threats by unifying SIEM, endpoint security, and cloud security in one place.',
}),
},
logo: {
altText: i18n.translate('home.guidedOnboarding.gettingStarted.security.iconName', {
defaultMessage: 'Security logo',
}),
},
navigateOptions: {
appId: 'securitySolutionUI',
path: '/overview',
},
},
};
export type UseCase = 'search' | 'observability' | 'security';
export interface UseCaseProps {
useCase: UseCase;
}
export const UseCaseCard = ({ useCase }: UseCaseProps) => {
const { application, trackUiMetric, uiSettings, http } = getServices();
const isDarkTheme = uiSettings.get('theme:darkMode');
const getImageUrl = (imageName: UseCase) => {
const imagePath = `/plugins/home/assets/solution_logos/${imageName}${
isDarkTheme ? '_dark' : ''
}.png`;
return http.basePath.prepend(imagePath);
};
const onUseCaseSelection = () => {
trackUiMetric(METRIC_TYPE.CLICK, `guided_onboarding__use_case__${useCase}`);
localStorage.setItem(`guidedOnboarding.${useCase}.tourActive`, JSON.stringify(true));
application.navigateToApp(constants[useCase].navigateOptions.appId, {
path: constants[useCase].navigateOptions.path,
});
};
const title = (
<EuiTitle size="xs">
<h4>
<strong>{constants[useCase].i18nTexts.title}</strong>
</h4>
</EuiTitle>
);
const description = (
<EuiText color="subdued" size="xs">
<p>{constants[useCase].i18nTexts.description}</p>
</EuiText>
);
return (
<EuiCard
display="subdued"
textAlign="left"
image={<EuiImage src={getImageUrl(useCase)} alt={constants[useCase].logo.altText} />}
title={title}
description={description}
// Used for FS tracking
data-test-subj={`onboarding--${useCase}UseCaseCard`}
onClick={onUseCaseSelection}
/>
);
};

View file

@ -20,6 +20,7 @@ import { UiCounterMetricType } from '@kbn/analytics';
import { UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import { DataViewsContract } from '@kbn/data-views-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import { GuidedOnboardingApi } from '@kbn/guided-onboarding-plugin/public';
import { TutorialService } from '../services/tutorials';
import { AddDataService } from '../services/add_data';
import { FeatureCatalogueRegistry } from '../services/feature_catalogue';
@ -49,6 +50,7 @@ export interface HomeKibanaServices {
tutorialService: TutorialService;
addDataService: AddDataService;
welcomeService: WelcomeService;
guidedOnboardingService?: GuidedOnboardingApi;
}
let services: HomeKibanaServices | null = null;

View file

@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public';
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { AppNavLinkStatus } from '@kbn/core/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import type { CloudSetup } from '@kbn/cloud-plugin/public';
@ -40,6 +41,7 @@ import {
export interface HomePluginStartDependencies {
dataViews: DataViewsPublicPluginStart;
urlForwarding: UrlForwardingStart;
guidedOnboarding: GuidedOnboardingPluginStart;
}
export interface HomePluginSetupDependencies {
@ -78,7 +80,7 @@ export class HomePublicPlugin
const trackUiMetric = usageCollection
? usageCollection.reportUiCounter.bind(usageCollection, 'Kibana_home')
: () => {};
const [coreStart, { dataViews, urlForwarding: urlForwardingStart }] =
const [coreStart, { dataViews, urlForwarding: urlForwardingStart, guidedOnboarding }] =
await core.getStartServices();
setServices({
share,
@ -102,6 +104,7 @@ export class HomePublicPlugin
addDataService: this.addDataService,
featureCatalogue: this.featuresCatalogueRegistry,
welcomeService: this.welcomeService,
guidedOnboardingService: guidedOnboarding.guidedOnboardingApi,
});
coreStart.chrome.docTitle.change(
i18n.translate('home.pageTitle', { defaultMessage: 'Home' })

View file

@ -16,6 +16,7 @@
{ "path": "../share/tsconfig.json" },
{ "path": "../url_forwarding/tsconfig.json" },
{ "path": "../usage_collection/tsconfig.json" },
{ "path": "../../../x-pack/plugins/cloud/tsconfig.json" }
{ "path": "../guided_onboarding/tsconfig.json" },
{ "path": "../../../x-pack/plugins/cloud/tsconfig.json" },
]
}

View file

@ -3102,15 +3102,6 @@
"home.breadcrumbs.integrationsAppTitle": "Intégrations",
"home.exploreButtonLabel": "Explorer par moi-même",
"home.exploreYourDataDescription": "Une fois toutes les étapes terminées, vous êtes prêt à explorer vos données.",
"home.guidedOnboarding.gettingStarted.observability.cardDescription": "Obtenez une observabilité de bout en bout de vos environnements en consolidant vos journaux, vos indicateurs et vos traces.",
"home.guidedOnboarding.gettingStarted.observability.cardTitle": "Monitorer mes environnements",
"home.guidedOnboarding.gettingStarted.observability.iconName": "Logo Observability",
"home.guidedOnboarding.gettingStarted.search.cardDescription": "Créez une expérience de recherche fine pour vos sites web, vos applications, votre contenu workplace, etc.",
"home.guidedOnboarding.gettingStarted.search.cardTitle": "Rechercher dans mes données",
"home.guidedOnboarding.gettingStarted.search.iconName": "Logo Entreprise Search",
"home.guidedOnboarding.gettingStarted.security.cardDescription": "Protégez votre environnement contre les menaces en unifiant SIEM, la sécurité des points de terminaison et la sécurité cloud en un seul endroit.",
"home.guidedOnboarding.gettingStarted.security.cardTitle": "Protéger mon environnement",
"home.guidedOnboarding.gettingStarted.security.iconName": "Logo Security",
"home.guidedOnboarding.gettingStarted.skip.buttonLabel": "Non merci, je vais explorer par moi-même.",
"home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle": "Sélectionnez un point de départ pour une visite rapide de la façon dont Elastic peut vous aider à faire encore plus avec vos données.",
"home.guidedOnboarding.gettingStarted.useCaseSelectionTitle": "Par quoi voulez-vous commencer ?",

View file

@ -3097,15 +3097,6 @@
"home.breadcrumbs.integrationsAppTitle": "統合",
"home.exploreButtonLabel": "独りで閲覧",
"home.exploreYourDataDescription": "すべてのステップを終えたら、データ閲覧準備の完了です。",
"home.guidedOnboarding.gettingStarted.observability.cardDescription": "ログ、メトリック、トレースを統合し、環境に対するエンドツーエンドのオブザーバビリティを実現します。",
"home.guidedOnboarding.gettingStarted.observability.cardTitle": "環境を監視",
"home.guidedOnboarding.gettingStarted.observability.iconName": "オブザーバビリティロゴ",
"home.guidedOnboarding.gettingStarted.search.cardDescription": "Webサイト、アプリケーション、workplaceコンテンツなどに合った、微調整された検索エクスペリエンスを作成します。",
"home.guidedOnboarding.gettingStarted.search.cardTitle": "データを検索",
"home.guidedOnboarding.gettingStarted.search.iconName": "エンタープライズ サーチロゴ",
"home.guidedOnboarding.gettingStarted.security.cardDescription": "SIEM、エンドポイントセキュリティ、クラウドセキュリティを一元化して統合することで、環境を脅威から守ります。",
"home.guidedOnboarding.gettingStarted.security.cardTitle": "環境を保護",
"home.guidedOnboarding.gettingStarted.security.iconName": "セキュリティロゴ",
"home.guidedOnboarding.gettingStarted.skip.buttonLabel": "いいえ、結構です。自分で探します。",
"home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle": "まず、スタートとしてクイックガイドを表示すると、どのようにElasticでデータに対して高度な操作を実行するのかを確認できます。",
"home.guidedOnboarding.gettingStarted.useCaseSelectionTitle": "最初に何をしたいですか?",

View file

@ -3102,15 +3102,6 @@
"home.breadcrumbs.integrationsAppTitle": "集成",
"home.exploreButtonLabel": "自己浏览",
"home.exploreYourDataDescription": "完成所有步骤后,您便可以随时浏览自己的数据。",
"home.guidedOnboarding.gettingStarted.observability.cardDescription": "通过整合您的日志、指标和跟踪,在您的环境中实现端到端可观测性。",
"home.guidedOnboarding.gettingStarted.observability.cardTitle": "监测我的环境",
"home.guidedOnboarding.gettingStarted.observability.iconName": "Observability 徽标",
"home.guidedOnboarding.gettingStarted.search.cardDescription": "为您的网站、应用程序、工作区内容等创建经过优化的搜索体验。",
"home.guidedOnboarding.gettingStarted.search.cardTitle": "搜索我的数据",
"home.guidedOnboarding.gettingStarted.search.iconName": "Enterprise Search 徽标",
"home.guidedOnboarding.gettingStarted.security.cardDescription": "通过在一个位置整合 SIEM、Endpoint Security 和云安全来保护您的环境,防止其受到威胁。",
"home.guidedOnboarding.gettingStarted.security.cardTitle": "保护我的环境",
"home.guidedOnboarding.gettingStarted.security.iconName": "安全徽标",
"home.guidedOnboarding.gettingStarted.skip.buttonLabel": "不用了,谢谢,我会自己浏览。",
"home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle": "选择快速教程的起点,了解 Elastic 如何帮助您利用数据完成更多任务。",
"home.guidedOnboarding.gettingStarted.useCaseSelectionTitle": "您希望先做什么?",

View file

@ -3493,6 +3493,10 @@
version "0.0.0"
uid ""
"@kbn/guided-onboarding@link:bazel-bin/packages/kbn-guided-onboarding":
version "0.0.0"
uid ""
"@kbn/handlebars@link:bazel-bin/packages/kbn-handlebars":
version "0.0.0"
uid ""
@ -7665,6 +7669,10 @@
version "0.0.0"
uid ""
"@types/kbn__guided-onboarding@link:bazel-bin/packages/kbn-guided-onboarding/npm_module_types":
version "0.0.0"
uid ""
"@types/kbn__handlebars@link:bazel-bin/packages/kbn-handlebars/npm_module_types":
version "0.0.0"
uid ""