mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Onboarding] Create guided_onboarding plugin (#138611)
* [Guided onboarding] Smashed commit of all POC work for guided onboarding and guided onboarding example plugins * [Guided onboarding] Fixed type errors * [Guided onboarding] Removed guidedOnboardingExample limit * [Guided onboarding] Fixed a functonal test for exposed configs * [Guided onboarding] Fixed plugin limit * [Guided onboarding] Added more information to the example plugin * [Guided onboarding] Fixed no-console error * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * [Guided onboarding] Fixed snake case errors * move guided_onboarding out of x-pack Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alison Goryachev <alison.goryachev@elastic.co>
This commit is contained in:
parent
7d6b5c6eb2
commit
95086f4365
41 changed files with 1666 additions and 0 deletions
|
@ -40,6 +40,7 @@
|
||||||
"eventAnnotation": "src/plugins/event_annotation",
|
"eventAnnotation": "src/plugins/event_annotation",
|
||||||
"fieldFormats": "src/plugins/field_formats",
|
"fieldFormats": "src/plugins/field_formats",
|
||||||
"flot": "packages/kbn-flot-charts/lib",
|
"flot": "packages/kbn-flot-charts/lib",
|
||||||
|
"guidedOnboarding": "src/plugins/guided_onboarding",
|
||||||
"home": "src/plugins/home",
|
"home": "src/plugins/home",
|
||||||
"homePackages": "packages/home",
|
"homePackages": "packages/home",
|
||||||
"indexPatternEditor": "src/plugins/data_view_editor",
|
"indexPatternEditor": "src/plugins/data_view_editor",
|
||||||
|
|
|
@ -176,6 +176,10 @@ for use in their own application.
|
||||||
|Index pattern fields formatters
|
|Index pattern fields formatters
|
||||||
|
|
||||||
|
|
||||||
|
|{kib-repo}blob/{branch}/src/plugins/guided_onboarding/README.md[guidedOnboarding]
|
||||||
|
|A Kibana plugin
|
||||||
|
|
||||||
|
|
||||||
|{kib-repo}blob/{branch}/src/plugins/home/README.md[home]
|
|{kib-repo}blob/{branch}/src/plugins/home/README.md[home]
|
||||||
|Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls.
|
|Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls.
|
||||||
|
|
||||||
|
|
7
examples/guided_onboarding_example/.i18nrc.json
Executable file
7
examples/guided_onboarding_example/.i18nrc.json
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"prefix": "guidedOnboardingExample",
|
||||||
|
"paths": {
|
||||||
|
"guidedOnboardingExample": "."
|
||||||
|
},
|
||||||
|
"translations": ["translations/ja-JP.json"]
|
||||||
|
}
|
9
examples/guided_onboarding_example/README.md
Executable file
9
examples/guided_onboarding_example/README.md
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
# guidedOnboardingExample
|
||||||
|
|
||||||
|
A Kibana plugin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
|
10
examples/guided_onboarding_example/common/index.ts
Executable file
10
examples/guided_onboarding_example/common/index.ts
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* 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 const PLUGIN_ID = 'guidedOnboardingExample';
|
||||||
|
export const PLUGIN_NAME = 'guidedOnboardingExample';
|
14
examples/guided_onboarding_example/kibana.json
Executable file
14
examples/guided_onboarding_example/kibana.json
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"id": "guidedOnboardingExample",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"kibanaVersion": "kibana",
|
||||||
|
"owner": {
|
||||||
|
"name": "platform-onboarding",
|
||||||
|
"githubTeam": "platform-onboarding"
|
||||||
|
},
|
||||||
|
"description": "Example plugin to consume guidedOnboarding",
|
||||||
|
"server": false,
|
||||||
|
"ui": true,
|
||||||
|
"requiredPlugins": ["navigation", "guidedOnboarding"],
|
||||||
|
"optionalPlugins": []
|
||||||
|
}
|
30
examples/guided_onboarding_example/public/application.tsx
Executable file
30
examples/guided_onboarding_example/public/application.tsx
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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 ReactDOM from 'react-dom';
|
||||||
|
import { AppMountParameters, CoreStart } from '@kbn/core/public';
|
||||||
|
import { AppPluginStartDependencies } from './types';
|
||||||
|
import { GuidedOnboardingExampleApp } from './components/app';
|
||||||
|
|
||||||
|
export const renderApp = (
|
||||||
|
{ notifications }: CoreStart,
|
||||||
|
{ guidedOnboarding }: AppPluginStartDependencies,
|
||||||
|
{ element, history }: AppMountParameters
|
||||||
|
) => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<GuidedOnboardingExampleApp
|
||||||
|
notifications={notifications}
|
||||||
|
guidedOnboarding={guidedOnboarding}
|
||||||
|
history={history}
|
||||||
|
/>,
|
||||||
|
element
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => ReactDOM.unmountComponentAtNode(element);
|
||||||
|
};
|
70
examples/guided_onboarding_example/public/components/app.tsx
Executable file
70
examples/guided_onboarding_example/public/components/app.tsx
Executable file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* 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 { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
|
||||||
|
import { Router, Switch, Route } from 'react-router-dom';
|
||||||
|
|
||||||
|
import {
|
||||||
|
EuiPage,
|
||||||
|
EuiPageBody,
|
||||||
|
EuiPageContent_Deprecated as EuiPageContent,
|
||||||
|
EuiPageHeader,
|
||||||
|
EuiTitle,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { CoreStart, ScopedHistory } from '@kbn/core/public';
|
||||||
|
|
||||||
|
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
|
||||||
|
import { StepTwo } from './step_two';
|
||||||
|
import { StepOne } from './step_one';
|
||||||
|
import { Main } from './main';
|
||||||
|
|
||||||
|
interface GuidedOnboardingExampleAppDeps {
|
||||||
|
notifications: CoreStart['notifications'];
|
||||||
|
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||||
|
history: ScopedHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps) => {
|
||||||
|
const { notifications, guidedOnboarding, history } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<I18nProvider>
|
||||||
|
<EuiPage restrictWidth="1000px">
|
||||||
|
<EuiPageBody>
|
||||||
|
<EuiPageHeader>
|
||||||
|
<EuiTitle size="l">
|
||||||
|
<h1>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.title"
|
||||||
|
defaultMessage="Guided onboarding examples"
|
||||||
|
/>
|
||||||
|
</h1>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageHeader>
|
||||||
|
<EuiPageContent>
|
||||||
|
<Router history={history}>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/">
|
||||||
|
<Main notifications={notifications} guidedOnboarding={guidedOnboarding} />
|
||||||
|
</Route>
|
||||||
|
<Route exact path="/stepOne">
|
||||||
|
<StepOne guidedOnboarding={guidedOnboarding} />
|
||||||
|
</Route>
|
||||||
|
<Route exact path="/stepTwo">
|
||||||
|
<StepTwo guidedOnboarding={guidedOnboarding} />
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Router>
|
||||||
|
</EuiPageContent>
|
||||||
|
</EuiPageBody>
|
||||||
|
</EuiPage>
|
||||||
|
</I18nProvider>
|
||||||
|
);
|
||||||
|
};
|
255
examples/guided_onboarding_example/public/components/main.tsx
Normal file
255
examples/guided_onboarding_example/public/components/main.tsx
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* 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, { useEffect, useState } from 'react';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiFieldNumber,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiFormRow,
|
||||||
|
EuiHorizontalRule,
|
||||||
|
EuiPageContentBody_Deprecated as EuiPageContentBody,
|
||||||
|
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
||||||
|
EuiSelect,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiText,
|
||||||
|
EuiTitle,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import {
|
||||||
|
GuidedOnboardingPluginStart,
|
||||||
|
GuidedOnboardingState,
|
||||||
|
UseCase,
|
||||||
|
} from '@kbn/guided-onboarding-plugin/public';
|
||||||
|
import { CoreStart } from '@kbn/core/public';
|
||||||
|
|
||||||
|
interface MainProps {
|
||||||
|
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||||
|
notifications: CoreStart['notifications'];
|
||||||
|
}
|
||||||
|
export const Main = (props: MainProps) => {
|
||||||
|
const {
|
||||||
|
guidedOnboarding: { guidedOnboardingApi },
|
||||||
|
notifications,
|
||||||
|
} = props;
|
||||||
|
const history = useHistory();
|
||||||
|
const [guideState, setGuideState] = useState<GuidedOnboardingState | undefined>(undefined);
|
||||||
|
|
||||||
|
const [selectedGuide, setSelectedGuide] = useState<
|
||||||
|
GuidedOnboardingState['activeGuide'] | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [selectedStep, setSelectedStep] = useState<GuidedOnboardingState['activeStep'] | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = guidedOnboardingApi?.fetchGuideState$().subscribe((newState) => {
|
||||||
|
setGuideState(newState);
|
||||||
|
});
|
||||||
|
return () => subscription?.unsubscribe();
|
||||||
|
}, [guidedOnboardingApi]);
|
||||||
|
|
||||||
|
const startGuide = async (guide: UseCase) => {
|
||||||
|
const response = await guidedOnboardingApi?.updateGuideState({
|
||||||
|
activeGuide: guide,
|
||||||
|
activeStep: 'add_data',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
notifications.toasts.addSuccess(
|
||||||
|
i18n.translate('guidedOnboardingExample.startGuide.toastLabel', {
|
||||||
|
defaultMessage: 'Guide (re-)started',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGuideState = async () => {
|
||||||
|
const response = await guidedOnboardingApi?.updateGuideState({
|
||||||
|
activeGuide: selectedGuide!,
|
||||||
|
activeStep: selectedStep!,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
notifications.toasts.addSuccess(
|
||||||
|
i18n.translate('guidedOnboardingExample.updateGuideState.toastLabel', {
|
||||||
|
defaultMessage: 'Guide state updated',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EuiPageContentHeader>
|
||||||
|
<EuiTitle>
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.title"
|
||||||
|
defaultMessage="Guided setup state"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageContentHeader>
|
||||||
|
<EuiPageContentBody>
|
||||||
|
<EuiText>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.currentStateTitle"
|
||||||
|
defaultMessage="Current state"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.state.explanation"
|
||||||
|
defaultMessage="The guide state on this page is updated automatically via an Observable,
|
||||||
|
so there is no need to 'load' the state from the server."
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{guideState ? (
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.state.activeGuideLabel"
|
||||||
|
defaultMessage="Active guide"
|
||||||
|
/>
|
||||||
|
</dt>
|
||||||
|
<dd>{guideState.activeGuide ?? 'undefined'}</dd>
|
||||||
|
|
||||||
|
<dt>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.state.activeStepLabel"
|
||||||
|
defaultMessage="Active step"
|
||||||
|
/>
|
||||||
|
</dt>
|
||||||
|
<dd>{guideState.activeStep ?? 'undefined'}</dd>
|
||||||
|
</dl>
|
||||||
|
) : undefined}
|
||||||
|
</EuiText>
|
||||||
|
<EuiHorizontalRule />
|
||||||
|
<EuiText>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.startGuide.title"
|
||||||
|
defaultMessage="(Re-)Start a guide"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiFlexGroup>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiButton onClick={() => startGuide('search')} fill>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.search.buttonLabel"
|
||||||
|
defaultMessage="(Re-)Start search guide"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiButton onClick={() => startGuide('observability')} fill>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.observability.buttonLabel"
|
||||||
|
defaultMessage="(Re-)Start observability guide"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiButton onClick={() => startGuide('security')} fill>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.security.label"
|
||||||
|
defaultMessage="(Re-)Start security guide"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiHorizontalRule />
|
||||||
|
<EuiText>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.setGuideState.title"
|
||||||
|
defaultMessage="Set guide state"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiFlexGroup>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFormRow label="Guide" helpText="Select a guide">
|
||||||
|
<EuiSelect
|
||||||
|
id={'guideSelect'}
|
||||||
|
options={[
|
||||||
|
{ value: 'observability', text: 'observability' },
|
||||||
|
{ value: 'security', text: 'security' },
|
||||||
|
{ value: 'search', text: 'search' },
|
||||||
|
{ value: '', text: 'unset' },
|
||||||
|
]}
|
||||||
|
value={selectedGuide}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value as UseCase;
|
||||||
|
const shouldResetState = value.trim().length === 0;
|
||||||
|
if (shouldResetState) {
|
||||||
|
setSelectedGuide(undefined);
|
||||||
|
setSelectedStep(undefined);
|
||||||
|
} else {
|
||||||
|
setSelectedGuide(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFormRow label="Step">
|
||||||
|
<EuiFieldNumber
|
||||||
|
value={selectedStep}
|
||||||
|
onChange={(e) => setSelectedStep(e.target.value)}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiFormRow hasEmptyLabelSpace>
|
||||||
|
<EuiButton onClick={updateGuideState}>Save</EuiButton>
|
||||||
|
</EuiFormRow>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiHorizontalRule />
|
||||||
|
<EuiText>
|
||||||
|
<h3>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.examplePages.title"
|
||||||
|
defaultMessage="Example pages"
|
||||||
|
/>
|
||||||
|
</h3>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiFlexGroup>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton onClick={() => history.push('stepOne')}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.examplePages.stepOne.link"
|
||||||
|
defaultMessage="Step 1"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton onClick={() => history.push('stepTwo')}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.main.examplePages.stepTwo.link"
|
||||||
|
defaultMessage="Step 2"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiPageContentBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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, { useEffect, useState } from 'react';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiText,
|
||||||
|
EuiTourStep,
|
||||||
|
EuiTitle,
|
||||||
|
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
||||||
|
EuiPageContentBody_Deprecated as EuiPageContentBody,
|
||||||
|
EuiSpacer,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
|
||||||
|
|
||||||
|
interface GuidedOnboardingExampleAppDeps {
|
||||||
|
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => {
|
||||||
|
const { guidedOnboardingApi } = guidedOnboarding;
|
||||||
|
|
||||||
|
const [isTourStepOpen, setIsTourStepOpen] = useState<boolean>(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = guidedOnboardingApi?.fetchGuideState$().subscribe((newState) => {
|
||||||
|
const { activeGuide: guide, activeStep: step } = newState;
|
||||||
|
|
||||||
|
if (guide === 'search' && step === 'add_data') {
|
||||||
|
setIsTourStepOpen(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return () => subscription?.unsubscribe();
|
||||||
|
}, [guidedOnboardingApi]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EuiPageContentHeader>
|
||||||
|
<EuiTitle>
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.stepOne.title"
|
||||||
|
defaultMessage="Example step Add data"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageContentHeader>
|
||||||
|
<EuiPageContentBody>
|
||||||
|
<EuiText>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.stepOne.explanation"
|
||||||
|
defaultMessage="The code on this page is listening to the guided setup state. If the state is set to
|
||||||
|
Search guide, step Add data, a EUI tour will be displayed, pointing to the button below. Alternatively,
|
||||||
|
the tour can be displayed via a localStorage value or a url param (see step 2)."
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiTourStep
|
||||||
|
content={
|
||||||
|
<EuiText>
|
||||||
|
<p>Click this button to complete step 1.</p>
|
||||||
|
</EuiText>
|
||||||
|
}
|
||||||
|
isStepOpen={isTourStepOpen}
|
||||||
|
minWidth={300}
|
||||||
|
onFinish={() => setIsTourStepOpen(false)}
|
||||||
|
step={1}
|
||||||
|
stepsTotal={1}
|
||||||
|
title="Step Add data"
|
||||||
|
anchorPosition="rightUp"
|
||||||
|
>
|
||||||
|
<EuiButton
|
||||||
|
onClick={async () => {
|
||||||
|
await guidedOnboardingApi?.updateGuideState({
|
||||||
|
activeGuide: 'search',
|
||||||
|
activeStep: 'search_experience',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Complete step 1
|
||||||
|
</EuiButton>
|
||||||
|
</EuiTourStep>
|
||||||
|
</EuiPageContentBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { EuiButton, EuiSpacer, EuiText, EuiTitle, EuiTourStep } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import {
|
||||||
|
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
|
||||||
|
EuiPageContentBody_Deprecated as EuiPageContentBody,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
interface StepTwoProps {
|
||||||
|
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StepTwo = (props: StepTwoProps) => {
|
||||||
|
const {
|
||||||
|
guidedOnboarding: { guidedOnboardingApi },
|
||||||
|
} = props;
|
||||||
|
const { search } = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const query = React.useMemo(() => new URLSearchParams(search), [search]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (query.get('showTour') === 'true') {
|
||||||
|
setIsTourStepOpen(true);
|
||||||
|
}
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
const [isTourStepOpen, setIsTourStepOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EuiPageContentHeader>
|
||||||
|
<EuiTitle>
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.stepTwo.title"
|
||||||
|
defaultMessage="Example step 2"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPageContentHeader>
|
||||||
|
<EuiPageContentBody>
|
||||||
|
<EuiText>
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="guidedOnboardingExample.guidesSelection.stepTwo.explanation"
|
||||||
|
defaultMessage="The EUI tour on this page is displayed, when a url param 'showTour' is set to 'true'."
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiTourStep
|
||||||
|
content={
|
||||||
|
<EuiText>
|
||||||
|
<p>Click this button to complete step 2.</p>
|
||||||
|
</EuiText>
|
||||||
|
}
|
||||||
|
isStepOpen={isTourStepOpen}
|
||||||
|
minWidth={300}
|
||||||
|
onFinish={() => {
|
||||||
|
history.push('/stepTwo');
|
||||||
|
query.set('showTour', 'false');
|
||||||
|
setIsTourStepOpen(false);
|
||||||
|
}}
|
||||||
|
step={1}
|
||||||
|
stepsTotal={1}
|
||||||
|
title="Step Add data"
|
||||||
|
anchorPosition="rightUp"
|
||||||
|
>
|
||||||
|
<EuiButton
|
||||||
|
onClick={async () => {
|
||||||
|
await guidedOnboardingApi?.updateGuideState({
|
||||||
|
activeGuide: 'search',
|
||||||
|
activeStep: 'optimize',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Complete step 2
|
||||||
|
</EuiButton>
|
||||||
|
</EuiTourStep>
|
||||||
|
</EuiPageContentBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
19
examples/guided_onboarding_example/public/index.ts
Executable file
19
examples/guided_onboarding_example/public/index.ts
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* 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 { GuidedOnboardingExamplePlugin } from './plugin';
|
||||||
|
|
||||||
|
// This exports static code and TypeScript types,
|
||||||
|
// as well as, Kibana Platform `plugin()` initializer.
|
||||||
|
export function plugin() {
|
||||||
|
return new GuidedOnboardingExamplePlugin();
|
||||||
|
}
|
||||||
|
export type {
|
||||||
|
GuidedOnboardingExamplePluginSetup,
|
||||||
|
GuidedOnboardingExamplePluginStart,
|
||||||
|
} from './types';
|
43
examples/guided_onboarding_example/public/plugin.ts
Executable file
43
examples/guided_onboarding_example/public/plugin.ts
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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 { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
|
||||||
|
import {
|
||||||
|
GuidedOnboardingExamplePluginSetup,
|
||||||
|
GuidedOnboardingExamplePluginStart,
|
||||||
|
AppPluginStartDependencies,
|
||||||
|
} from './types';
|
||||||
|
import { PLUGIN_NAME } from '../common';
|
||||||
|
|
||||||
|
export class GuidedOnboardingExamplePlugin
|
||||||
|
implements Plugin<GuidedOnboardingExamplePluginSetup, GuidedOnboardingExamplePluginStart>
|
||||||
|
{
|
||||||
|
public setup(core: CoreSetup): GuidedOnboardingExamplePluginSetup {
|
||||||
|
// Register an application into the side navigation menu
|
||||||
|
core.application.register({
|
||||||
|
id: 'guidedOnboardingExample',
|
||||||
|
title: PLUGIN_NAME,
|
||||||
|
async mount(params: AppMountParameters) {
|
||||||
|
// Load application bundle
|
||||||
|
const { renderApp } = await import('./application');
|
||||||
|
// Get start services as specified in kibana.json
|
||||||
|
const [coreStart, depsStart] = await core.getStartServices();
|
||||||
|
// Render the application
|
||||||
|
return renderApp(coreStart, depsStart as AppPluginStartDependencies, params);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(core: CoreStart): GuidedOnboardingExamplePluginStart {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {}
|
||||||
|
}
|
21
examples/guided_onboarding_example/public/types.ts
Executable file
21
examples/guided_onboarding_example/public/types.ts
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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 { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||||
|
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface GuidedOnboardingExamplePluginSetup {}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface GuidedOnboardingExamplePluginStart {}
|
||||||
|
|
||||||
|
export interface AppPluginStartDependencies {
|
||||||
|
navigation: NavigationPublicPluginStart;
|
||||||
|
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||||
|
}
|
24
examples/guided_onboarding_example/tsconfig.json
Normal file
24
examples/guided_onboarding_example/tsconfig.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./target/types",
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"__jest__/**/*",
|
||||||
|
"common/**/*",
|
||||||
|
"public/**/*",
|
||||||
|
"server/**/*",
|
||||||
|
"../../typings/**/*",
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../src/core/tsconfig.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../src/plugins/guided_onboarding/tsconfig.json"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
|
@ -56,6 +56,7 @@ pageLoadAssetSize:
|
||||||
globalSearchProviders: 25554
|
globalSearchProviders: 25554
|
||||||
graph: 31504
|
graph: 31504
|
||||||
grokdebugger: 26779
|
grokdebugger: 26779
|
||||||
|
guidedOnboarding: 26875
|
||||||
home: 30182
|
home: 30182
|
||||||
indexLifecycleManagement: 107090
|
indexLifecycleManagement: 107090
|
||||||
indexManagement: 140608
|
indexManagement: 140608
|
||||||
|
|
|
@ -59,6 +59,7 @@ const previouslyRegisteredTypes = [
|
||||||
'fleet-enrollment-api-keys',
|
'fleet-enrollment-api-keys',
|
||||||
'fleet-preconfiguration-deletion-record',
|
'fleet-preconfiguration-deletion-record',
|
||||||
'graph-workspace',
|
'graph-workspace',
|
||||||
|
'guided-setup-state',
|
||||||
'index-pattern',
|
'index-pattern',
|
||||||
'infrastructure-monitoring-log-view',
|
'infrastructure-monitoring-log-view',
|
||||||
'infrastructure-ui-source',
|
'infrastructure-ui-source',
|
||||||
|
|
7
src/plugins/guided_onboarding/.i18nrc.json
Executable file
7
src/plugins/guided_onboarding/.i18nrc.json
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"prefix": "guidedOnboarding",
|
||||||
|
"paths": {
|
||||||
|
"guidedOnboarding": "."
|
||||||
|
},
|
||||||
|
"translations": ["translations/ja-JP.json"]
|
||||||
|
}
|
9
src/plugins/guided_onboarding/README.md
Executable file
9
src/plugins/guided_onboarding/README.md
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
# guidedOnboarding
|
||||||
|
|
||||||
|
A Kibana plugin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
|
12
src/plugins/guided_onboarding/common/index.ts
Executable file
12
src/plugins/guided_onboarding/common/index.ts
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* 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 const PLUGIN_ID = 'guidedOnboarding';
|
||||||
|
export const PLUGIN_NAME = 'guidedOnboarding';
|
||||||
|
|
||||||
|
export const API_BASE_PATH = '/api/guided_onboarding';
|
15
src/plugins/guided_onboarding/kibana.json
Executable file
15
src/plugins/guided_onboarding/kibana.json
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"id": "guidedOnboarding",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"kibanaVersion": "kibana",
|
||||||
|
"owner": {
|
||||||
|
"name": "Journey Onboarding",
|
||||||
|
"githubTeam": "platform-onboarding"
|
||||||
|
},
|
||||||
|
"description": "Guided onboarding framework",
|
||||||
|
"server": true,
|
||||||
|
"ui": true,
|
||||||
|
"requiredBundles": ["kibanaReact"],
|
||||||
|
"optionalPlugins": [],
|
||||||
|
"configPath": ["guidedOnboarding"]
|
||||||
|
}
|
9
src/plugins/guided_onboarding/public/components/index.ts
Normal file
9
src/plugins/guided_onboarding/public/components/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* 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 { GuidedOnboardingButton } from './onboarding_button';
|
|
@ -0,0 +1,241 @@
|
||||||
|
/*
|
||||||
|
* 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, useEffect, useRef } from 'react';
|
||||||
|
import { css } from '@emotion/react';
|
||||||
|
import {
|
||||||
|
EuiPopover,
|
||||||
|
EuiPopoverTitle,
|
||||||
|
EuiPopoverFooter,
|
||||||
|
EuiButton,
|
||||||
|
EuiText,
|
||||||
|
EuiProgress,
|
||||||
|
EuiAccordion,
|
||||||
|
EuiHorizontalRule,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiTextColor,
|
||||||
|
htmlIdGenerator,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiIcon,
|
||||||
|
useEuiTheme,
|
||||||
|
EuiButtonEmpty,
|
||||||
|
EuiTitle,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
|
||||||
|
import { ApplicationStart } from '@kbn/core-application-browser';
|
||||||
|
import { HttpStart } from '@kbn/core-http-browser';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { guidesConfig } from '../constants';
|
||||||
|
import type { GuideConfig, StepStatus, GuidedOnboardingState, StepConfig } from '../types';
|
||||||
|
import type { ApiService } from '../services/api';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
api: ApiService;
|
||||||
|
application: ApplicationStart;
|
||||||
|
http: HttpStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getConfig = (state?: GuidedOnboardingState): GuideConfig | undefined => {
|
||||||
|
if (state?.activeGuide && state.activeGuide !== 'unset') {
|
||||||
|
return guidesConfig[state.activeGuide];
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStepLabel = (steps?: StepConfig[], state?: GuidedOnboardingState): string => {
|
||||||
|
if (steps && state?.activeStep) {
|
||||||
|
const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === state.activeStep);
|
||||||
|
if (activeStepIndex > -1) {
|
||||||
|
return `: Step ${activeStepIndex + 1}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStepStatus = (steps: StepConfig[], stepIndex: number, activeStep?: string): StepStatus => {
|
||||||
|
const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === activeStep);
|
||||||
|
if (activeStepIndex < stepIndex) {
|
||||||
|
return 'incomplete';
|
||||||
|
}
|
||||||
|
if (activeStepIndex === stepIndex) {
|
||||||
|
return 'in_progress';
|
||||||
|
}
|
||||||
|
return 'complete';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GuidedOnboardingButton = ({ api, application, http }: Props) => {
|
||||||
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
|
|
||||||
|
const [guidedOnboardingState, setGuidedOnboardingState] = useState<
|
||||||
|
GuidedOnboardingState | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
const firstRender = useRef(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = api.fetchGuideState$().subscribe((newState) => {
|
||||||
|
if (
|
||||||
|
guidedOnboardingState?.activeGuide !== newState.activeGuide ||
|
||||||
|
guidedOnboardingState?.activeStep !== newState.activeStep
|
||||||
|
) {
|
||||||
|
if (firstRender.current) {
|
||||||
|
firstRender.current = false;
|
||||||
|
} else {
|
||||||
|
setIsPopoverOpen(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setGuidedOnboardingState(newState);
|
||||||
|
});
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [api, guidedOnboardingState?.activeGuide, guidedOnboardingState?.activeStep]);
|
||||||
|
|
||||||
|
const { euiTheme } = useEuiTheme();
|
||||||
|
|
||||||
|
const togglePopover = () => {
|
||||||
|
setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const popoverContainerCss = css`
|
||||||
|
width: 400px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const statusCircleCss = ({ status }: { status: StepStatus }) => css`
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 32px;
|
||||||
|
${(status === 'complete' || status === 'in_progress') &&
|
||||||
|
`background-color: ${euiTheme.colors.success};`}
|
||||||
|
${status === 'incomplete' &&
|
||||||
|
`
|
||||||
|
border: 2px solid ${euiTheme.colors.lightShade};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const guideConfig = getConfig(guidedOnboardingState);
|
||||||
|
const stepLabel = getStepLabel(guideConfig?.steps, guidedOnboardingState);
|
||||||
|
|
||||||
|
const navigateToStep = (step: StepConfig) => {
|
||||||
|
setIsPopoverOpen(false);
|
||||||
|
if (step.location) {
|
||||||
|
application.navigateToApp(step.location.appID, { path: step.location.path });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return guideConfig ? (
|
||||||
|
<EuiPopover
|
||||||
|
button={
|
||||||
|
<EuiButton onClick={togglePopover} color="success" fill>
|
||||||
|
{i18n.translate('guidedOnboarding.guidedSetupButtonLabel', {
|
||||||
|
defaultMessage: 'Guided setup{stepLabel}',
|
||||||
|
values: {
|
||||||
|
stepLabel,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
</EuiButton>
|
||||||
|
}
|
||||||
|
isOpen={isPopoverOpen}
|
||||||
|
closePopover={() => setIsPopoverOpen(false)}
|
||||||
|
anchorPosition="downRight"
|
||||||
|
hasArrow={false}
|
||||||
|
offset={10}
|
||||||
|
panelPaddingSize="l"
|
||||||
|
>
|
||||||
|
<EuiPopoverTitle>
|
||||||
|
<EuiButtonEmpty
|
||||||
|
onClick={() => {}}
|
||||||
|
iconSide="left"
|
||||||
|
iconType="arrowLeft"
|
||||||
|
isDisabled={true}
|
||||||
|
flush="left"
|
||||||
|
>
|
||||||
|
{i18n.translate('guidedOnboarding.dropdownPanel.backToGuidesLink', {
|
||||||
|
defaultMessage: 'Back to guides',
|
||||||
|
})}
|
||||||
|
</EuiButtonEmpty>
|
||||||
|
<EuiTitle size="m">
|
||||||
|
<h3>{guideConfig?.title}</h3>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiPopoverTitle>
|
||||||
|
|
||||||
|
<div css={popoverContainerCss}>
|
||||||
|
<EuiText>
|
||||||
|
<p>{guideConfig?.description}</p>
|
||||||
|
</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
<EuiHorizontalRule />
|
||||||
|
<EuiProgress label="Progress" value={40} max={100} size="l" valueText />
|
||||||
|
<EuiSpacer size="xl" />
|
||||||
|
{guideConfig?.steps.map((step, index, steps) => {
|
||||||
|
const accordionId = htmlIdGenerator(`accordion${index}`)();
|
||||||
|
|
||||||
|
const stepStatus = getStepStatus(steps, index, guidedOnboardingState?.activeStep);
|
||||||
|
const buttonContent = (
|
||||||
|
<EuiFlexGroup gutterSize="s">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<span css={statusCircleCss({ status: stepStatus })} className="eui-textCenter">
|
||||||
|
<span className="euiScreenReaderOnly">{stepStatus}</span>
|
||||||
|
{stepStatus === 'complete' && <EuiIcon type="check" color="white" />}
|
||||||
|
</span>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>{step.title}</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<EuiAccordion
|
||||||
|
id={accordionId}
|
||||||
|
buttonContent={buttonContent}
|
||||||
|
arrowDisplay="right"
|
||||||
|
forceState={stepStatus === 'in_progress' ? 'open' : 'closed'}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<EuiText size="s">{step.description}</EuiText>
|
||||||
|
<EuiSpacer />
|
||||||
|
{stepStatus === 'in_progress' && (
|
||||||
|
<EuiFlexGroup justifyContent="flexEnd">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton onClick={() => navigateToStep(step)} fill>
|
||||||
|
{/* TODO: Support for conditional "Continue" button label if user revists a step */}
|
||||||
|
{i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', {
|
||||||
|
defaultMessage: 'Start',
|
||||||
|
})}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</EuiAccordion>
|
||||||
|
|
||||||
|
{/* Do not show horizontal rule for last item */}
|
||||||
|
{guideConfig.steps.length - 1 !== index && <EuiHorizontalRule margin="m" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<EuiPopoverFooter>
|
||||||
|
<EuiText size="xs" textAlign="center">
|
||||||
|
<EuiTextColor color="subdued">
|
||||||
|
<p>
|
||||||
|
{i18n.translate('guidedOnboarding.dropdownPanel.footerDescription', {
|
||||||
|
defaultMessage: `Got questions? We're here to help.`,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</EuiTextColor>
|
||||||
|
</EuiText>
|
||||||
|
</EuiPopoverFooter>
|
||||||
|
</div>
|
||||||
|
</EuiPopover>
|
||||||
|
) : (
|
||||||
|
<EuiButton onClick={togglePopover} color="success" fill isDisabled={true}>
|
||||||
|
Guided setup
|
||||||
|
</EuiButton>
|
||||||
|
);
|
||||||
|
};
|
22
src/plugins/guided_onboarding/public/constants/index.ts
Normal file
22
src/plugins/guided_onboarding/public/constants/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* 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 { securityConfig } from './security';
|
||||||
|
import { observabilityConfig } from './observability';
|
||||||
|
import { searchConfig } from './search';
|
||||||
|
import type { GuideConfig, UseCase } from '../types';
|
||||||
|
|
||||||
|
type GuidesConfig = {
|
||||||
|
[key in UseCase]: GuideConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const guidesConfig: GuidesConfig = {
|
||||||
|
security: securityConfig,
|
||||||
|
observability: observabilityConfig,
|
||||||
|
search: searchConfig,
|
||||||
|
};
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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 type { GuideConfig } from '../types';
|
||||||
|
|
||||||
|
export const observabilityConfig: GuideConfig = {
|
||||||
|
title: 'Observe my infrastructure',
|
||||||
|
description:
|
||||||
|
'The foundation of seeing Elastic in action, is adding you own data. Follow links to our documents below to learn more.',
|
||||||
|
docs: {
|
||||||
|
text: 'Observability 101 Documentation',
|
||||||
|
url: 'example.com',
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: 'add_data',
|
||||||
|
title: 'Add data',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rules',
|
||||||
|
title: 'Customize your alerting rules',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'infrastructure',
|
||||||
|
title: 'View infrastructure details',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'explore',
|
||||||
|
title: 'Explore Discover and Dashboards',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tour',
|
||||||
|
title: 'Tour Observability',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'do_more',
|
||||||
|
title: 'Do more with Observability',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
52
src/plugins/guided_onboarding/public/constants/search.ts
Normal file
52
src/plugins/guided_onboarding/public/constants/search.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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 type { GuideConfig } from '../types';
|
||||||
|
|
||||||
|
export const searchConfig: GuideConfig = {
|
||||||
|
title: 'Search my data',
|
||||||
|
description: `We'll help you build world-class search experiences with your data.`,
|
||||||
|
docs: {
|
||||||
|
text: 'Enterprise Search 101 Documentation',
|
||||||
|
url: 'example.com',
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: 'add_data',
|
||||||
|
title: 'Add data',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
location: {
|
||||||
|
appID: 'guidedOnboardingExample',
|
||||||
|
path: 'stepOne',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'search_experience',
|
||||||
|
title: 'Build a search experience',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
location: {
|
||||||
|
appID: 'guidedOnboardingExample',
|
||||||
|
path: 'stepTwo?showTour=true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'optimize',
|
||||||
|
title: 'Optimize your search relevance',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'review',
|
||||||
|
title: 'Review your search analytics',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
43
src/plugins/guided_onboarding/public/constants/security.ts
Normal file
43
src/plugins/guided_onboarding/public/constants/security.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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 type { GuideConfig } from '../types';
|
||||||
|
|
||||||
|
export const securityConfig: GuideConfig = {
|
||||||
|
title: 'Get started with SIEM',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: 'add_data',
|
||||||
|
title: 'Add and view your data',
|
||||||
|
description:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam ligula enim, malesuada a finibus vel, cursus sed risus. Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'rules',
|
||||||
|
title: 'Turn on rules',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'alerts',
|
||||||
|
title: 'View Alerts',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cases',
|
||||||
|
title: 'Cases and investigations',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'do_more',
|
||||||
|
title: 'Do more with Elastic Security',
|
||||||
|
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
20
src/plugins/guided_onboarding/public/index.ts
Executable file
20
src/plugins/guided_onboarding/public/index.ts
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* 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 { PluginInitializerContext } from '@kbn/core/public';
|
||||||
|
import { GuidedOnboardingPlugin } from './plugin';
|
||||||
|
|
||||||
|
export function plugin(ctx: PluginInitializerContext) {
|
||||||
|
return new GuidedOnboardingPlugin(ctx);
|
||||||
|
}
|
||||||
|
export type {
|
||||||
|
GuidedOnboardingPluginSetup,
|
||||||
|
GuidedOnboardingPluginStart,
|
||||||
|
GuidedOnboardingState,
|
||||||
|
UseCase,
|
||||||
|
} from './types';
|
94
src/plugins/guided_onboarding/public/plugin.tsx
Executable file
94
src/plugins/guided_onboarding/public/plugin.tsx
Executable file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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 ReactDOM from 'react-dom';
|
||||||
|
import React from 'react';
|
||||||
|
import * as Rx from 'rxjs';
|
||||||
|
import { I18nProvider } from '@kbn/i18n-react';
|
||||||
|
import {
|
||||||
|
CoreSetup,
|
||||||
|
CoreStart,
|
||||||
|
Plugin,
|
||||||
|
CoreTheme,
|
||||||
|
ApplicationStart,
|
||||||
|
HttpStart,
|
||||||
|
PluginInitializerContext,
|
||||||
|
} from '@kbn/core/public';
|
||||||
|
|
||||||
|
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
|
||||||
|
import {
|
||||||
|
ClientConfigType,
|
||||||
|
GuidedOnboardingPluginSetup,
|
||||||
|
GuidedOnboardingPluginStart,
|
||||||
|
} from './types';
|
||||||
|
import { GuidedOnboardingButton } from './components';
|
||||||
|
import { ApiService, apiService } from './services/api';
|
||||||
|
|
||||||
|
export class GuidedOnboardingPlugin
|
||||||
|
implements Plugin<GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart>
|
||||||
|
{
|
||||||
|
constructor(private ctx: PluginInitializerContext) {}
|
||||||
|
public setup(core: CoreSetup): GuidedOnboardingPluginSetup {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(core: CoreStart): GuidedOnboardingPluginStart {
|
||||||
|
const { ui: isGuidedOnboardingUiEnabled } = this.ctx.config.get<ClientConfigType>();
|
||||||
|
if (!isGuidedOnboardingUiEnabled) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { chrome, http, theme, application } = core;
|
||||||
|
|
||||||
|
// Initialize services
|
||||||
|
apiService.setup(http);
|
||||||
|
|
||||||
|
chrome.navControls.registerExtension({
|
||||||
|
order: 1000,
|
||||||
|
mount: (target) =>
|
||||||
|
this.mount({
|
||||||
|
targetDomElement: target,
|
||||||
|
theme$: theme.theme$,
|
||||||
|
api: apiService,
|
||||||
|
application,
|
||||||
|
http,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return methods that should be available to other plugins
|
||||||
|
return {
|
||||||
|
guidedOnboardingApi: apiService,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {}
|
||||||
|
|
||||||
|
private mount({
|
||||||
|
targetDomElement,
|
||||||
|
theme$,
|
||||||
|
api,
|
||||||
|
application,
|
||||||
|
http,
|
||||||
|
}: {
|
||||||
|
targetDomElement: HTMLElement;
|
||||||
|
theme$: Rx.Observable<CoreTheme>;
|
||||||
|
api: ApiService;
|
||||||
|
application: ApplicationStart;
|
||||||
|
http: HttpStart;
|
||||||
|
}) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<KibanaThemeProvider theme$={theme$}>
|
||||||
|
<I18nProvider>
|
||||||
|
<GuidedOnboardingButton api={api} application={application} http={http} />
|
||||||
|
</I18nProvider>
|
||||||
|
</KibanaThemeProvider>,
|
||||||
|
targetDomElement
|
||||||
|
);
|
||||||
|
return () => ReactDOM.unmountComponentAtNode(targetDomElement);
|
||||||
|
}
|
||||||
|
}
|
59
src/plugins/guided_onboarding/public/services/api.ts
Normal file
59
src/plugins/guided_onboarding/public/services/api.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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 { HttpSetup } from '@kbn/core/public';
|
||||||
|
import { BehaviorSubject, map, from, concatMap, of } from 'rxjs';
|
||||||
|
|
||||||
|
import { API_BASE_PATH } from '../../common';
|
||||||
|
import { GuidedOnboardingState } from '../types';
|
||||||
|
|
||||||
|
export class ApiService {
|
||||||
|
private client: HttpSetup | undefined;
|
||||||
|
private onboardingGuideState$!: BehaviorSubject<GuidedOnboardingState | undefined>;
|
||||||
|
|
||||||
|
public setup(httpClient: HttpSetup): void {
|
||||||
|
this.client = httpClient;
|
||||||
|
this.onboardingGuideState$ = new BehaviorSubject<GuidedOnboardingState | undefined>(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetchGuideState$() {
|
||||||
|
// TODO add error handling if this.client has not been initialized or request fails
|
||||||
|
return this.onboardingGuideState$.pipe(
|
||||||
|
concatMap((state) =>
|
||||||
|
state === undefined
|
||||||
|
? from(this.client!.get<{ state: GuidedOnboardingState }>(`${API_BASE_PATH}/state`)).pipe(
|
||||||
|
map((response) => response.state)
|
||||||
|
)
|
||||||
|
: of(state)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateGuideState(newState: GuidedOnboardingState) {
|
||||||
|
if (!this.client) {
|
||||||
|
throw new Error('ApiService has not be initialized.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.client.put<{ state: GuidedOnboardingState }>(
|
||||||
|
`${API_BASE_PATH}/state`,
|
||||||
|
{
|
||||||
|
body: JSON.stringify(newState),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.onboardingGuideState$.next(newState);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
// TODO handle error
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const apiService = new ApiService();
|
54
src/plugins/guided_onboarding/public/types.ts
Executable file
54
src/plugins/guided_onboarding/public/types.ts
Executable file
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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 { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
|
||||||
|
import { ApiService } from './services/api';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface GuidedOnboardingPluginSetup {}
|
||||||
|
|
||||||
|
export interface GuidedOnboardingPluginStart {
|
||||||
|
guidedOnboardingApi?: ApiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppPluginStartDependencies {
|
||||||
|
navigation: NavigationPublicPluginStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseCase = 'observability' | 'security' | 'search';
|
||||||
|
export type StepStatus = 'incomplete' | 'complete' | 'in_progress';
|
||||||
|
|
||||||
|
export interface StepConfig {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
location?: {
|
||||||
|
appID: string;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
status?: StepStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuideConfig {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
docs?: {
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
steps: StepConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuidedOnboardingState {
|
||||||
|
activeGuide: UseCase | 'unset';
|
||||||
|
activeStep: string | 'unset';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientConfigType {
|
||||||
|
ui: boolean;
|
||||||
|
}
|
25
src/plugins/guided_onboarding/server/config.ts
Normal file
25
src/plugins/guided_onboarding/server/config.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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 { PluginConfigDescriptor } from '@kbn/core/server';
|
||||||
|
import { schema, TypeOf } from '@kbn/config-schema';
|
||||||
|
|
||||||
|
// By default, hide any guided onboarding UI. Change it with guidedOnboarding.ui:true in kibana.dev.yml
|
||||||
|
const configSchema = schema.object({
|
||||||
|
ui: schema.boolean({ defaultValue: false }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type GuidedOnboardingConfig = TypeOf<typeof configSchema>;
|
||||||
|
|
||||||
|
export const config: PluginConfigDescriptor<GuidedOnboardingConfig> = {
|
||||||
|
// define which config properties should be available in the client side plugin
|
||||||
|
exposeToBrowser: {
|
||||||
|
ui: true,
|
||||||
|
},
|
||||||
|
schema: configSchema,
|
||||||
|
};
|
18
src/plugins/guided_onboarding/server/index.ts
Executable file
18
src/plugins/guided_onboarding/server/index.ts
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* 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 { PluginInitializerContext } from '@kbn/core/server';
|
||||||
|
import { GuidedOnboardingPlugin } from './plugin';
|
||||||
|
|
||||||
|
export { config } from './config';
|
||||||
|
|
||||||
|
export function plugin(initializerContext: PluginInitializerContext) {
|
||||||
|
return new GuidedOnboardingPlugin(initializerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types';
|
43
src/plugins/guided_onboarding/server/plugin.ts
Executable file
43
src/plugins/guided_onboarding/server/plugin.ts
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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 { PluginInitializerContext, CoreSetup, Plugin, Logger } from '@kbn/core/server';
|
||||||
|
|
||||||
|
import { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types';
|
||||||
|
import { defineRoutes } from './routes';
|
||||||
|
import { guidedSetupSavedObjects } from './saved_objects';
|
||||||
|
|
||||||
|
export class GuidedOnboardingPlugin
|
||||||
|
implements Plugin<GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart>
|
||||||
|
{
|
||||||
|
private readonly logger: Logger;
|
||||||
|
|
||||||
|
constructor(initializerContext: PluginInitializerContext) {
|
||||||
|
this.logger = initializerContext.logger.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setup(core: CoreSetup) {
|
||||||
|
this.logger.debug('guidedOnboarding: Setup');
|
||||||
|
const router = core.http.createRouter();
|
||||||
|
|
||||||
|
// Register server side APIs
|
||||||
|
defineRoutes(router);
|
||||||
|
|
||||||
|
// register saved objects
|
||||||
|
core.savedObjects.registerType(guidedSetupSavedObjects);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
this.logger.debug('guidedOnboarding: Started');
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {}
|
||||||
|
}
|
101
src/plugins/guided_onboarding/server/routes/index.ts
Executable file
101
src/plugins/guided_onboarding/server/routes/index.ts
Executable file
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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 { schema } from '@kbn/config-schema';
|
||||||
|
import { IRouter, SavedObjectsClient } from '@kbn/core/server';
|
||||||
|
import {
|
||||||
|
guidedSetupDefaultState,
|
||||||
|
guidedSetupSavedObjectsId,
|
||||||
|
guidedSetupSavedObjectsType,
|
||||||
|
} from '../saved_objects';
|
||||||
|
|
||||||
|
const doesGuidedSetupExist = async (savedObjectsClient: SavedObjectsClient): Promise<boolean> => {
|
||||||
|
return savedObjectsClient
|
||||||
|
.find({ type: guidedSetupSavedObjectsType })
|
||||||
|
.then((foundSavedObjects) => foundSavedObjects.total > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function defineRoutes(router: IRouter) {
|
||||||
|
router.get(
|
||||||
|
{
|
||||||
|
path: '/api/guided_onboarding/state',
|
||||||
|
validate: false,
|
||||||
|
},
|
||||||
|
async (context, request, response) => {
|
||||||
|
const coreContext = await context.core;
|
||||||
|
const soClient = coreContext.savedObjects.client as SavedObjectsClient;
|
||||||
|
|
||||||
|
const stateExists = await doesGuidedSetupExist(soClient);
|
||||||
|
if (stateExists) {
|
||||||
|
const guidedSetupSO = await soClient.get(
|
||||||
|
guidedSetupSavedObjectsType,
|
||||||
|
guidedSetupSavedObjectsId
|
||||||
|
);
|
||||||
|
return response.ok({
|
||||||
|
body: { state: guidedSetupSO.attributes },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return response.ok({
|
||||||
|
body: { state: guidedSetupDefaultState },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put(
|
||||||
|
{
|
||||||
|
path: '/api/guided_onboarding/state',
|
||||||
|
validate: {
|
||||||
|
body: schema.object({
|
||||||
|
activeGuide: schema.maybe(schema.string()),
|
||||||
|
activeStep: schema.maybe(schema.string()),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (context, request, response) => {
|
||||||
|
const activeGuide = request.body.activeGuide;
|
||||||
|
const activeStep = request.body.activeStep;
|
||||||
|
const attributes = {
|
||||||
|
activeGuide: activeGuide ?? 'unset',
|
||||||
|
activeStep: activeStep ?? 'unset',
|
||||||
|
};
|
||||||
|
const coreContext = await context.core;
|
||||||
|
const soClient = coreContext.savedObjects.client as SavedObjectsClient;
|
||||||
|
|
||||||
|
const stateExists = await doesGuidedSetupExist(soClient);
|
||||||
|
|
||||||
|
if (stateExists) {
|
||||||
|
const updatedGuidedSetupSO = await soClient.update(
|
||||||
|
guidedSetupSavedObjectsType,
|
||||||
|
guidedSetupSavedObjectsId,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
return response.ok({
|
||||||
|
body: { state: updatedGuidedSetupSO.attributes },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const guidedSetupSO = await soClient.create(
|
||||||
|
guidedSetupSavedObjectsType,
|
||||||
|
{
|
||||||
|
...guidedSetupDefaultState,
|
||||||
|
...attributes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: guidedSetupSavedObjectsId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.ok({
|
||||||
|
body: {
|
||||||
|
state: guidedSetupSO.attributes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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 { SavedObjectsType } from '@kbn/core/server';
|
||||||
|
|
||||||
|
export const guidedSetupSavedObjectsType = 'guided-setup-state';
|
||||||
|
export const guidedSetupSavedObjectsId = 'guided-setup-state-id';
|
||||||
|
export const guidedSetupDefaultState = {
|
||||||
|
activeGuide: 'unset',
|
||||||
|
activeStep: 'unset',
|
||||||
|
};
|
||||||
|
export const guidedSetupSavedObjects: SavedObjectsType = {
|
||||||
|
name: guidedSetupSavedObjectsType,
|
||||||
|
hidden: false,
|
||||||
|
// make it available in all spaces for now
|
||||||
|
namespaceType: 'agnostic',
|
||||||
|
mappings: {
|
||||||
|
dynamic: false,
|
||||||
|
properties: {
|
||||||
|
activeGuide: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
activeStep: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
14
src/plugins/guided_onboarding/server/saved_objects/index.ts
Normal file
14
src/plugins/guided_onboarding/server/saved_objects/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
guidedSetupSavedObjects,
|
||||||
|
guidedSetupSavedObjectsType,
|
||||||
|
guidedSetupSavedObjectsId,
|
||||||
|
guidedSetupDefaultState,
|
||||||
|
} from './guided_setup';
|
13
src/plugins/guided_onboarding/server/types.ts
Executable file
13
src/plugins/guided_onboarding/server/types.ts
Executable 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface GuidedOnboardingPluginSetup {}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
|
export interface GuidedOnboardingPluginStart {}
|
22
src/plugins/guided_onboarding/tsconfig.json
Normal file
22
src/plugins/guided_onboarding/tsconfig.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./target/types",
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true
|
||||||
|
},
|
||||||
|
"include": ["common/**/*", "public/**/*", "server/**/*"],
|
||||||
|
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../core/tsconfig.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../navigation/tsconfig.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../kibana_react/tsconfig.json"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
|
@ -106,6 +106,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
||||||
'data.search.sessions.pageSize (number)',
|
'data.search.sessions.pageSize (number)',
|
||||||
'data.search.sessions.trackingInterval (duration)',
|
'data.search.sessions.trackingInterval (duration)',
|
||||||
'enterpriseSearch.host (string)',
|
'enterpriseSearch.host (string)',
|
||||||
|
'guidedOnboarding.ui (boolean)',
|
||||||
'home.disableWelcomeScreen (boolean)',
|
'home.disableWelcomeScreen (boolean)',
|
||||||
'map.emsFileApiUrl (string)',
|
'map.emsFileApiUrl (string)',
|
||||||
'map.emsFontLibraryUrl (string)',
|
'map.emsFontLibraryUrl (string)',
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
"@kbn/expressions-explorer-plugin/*": ["examples/expressions_explorer/*"],
|
"@kbn/expressions-explorer-plugin/*": ["examples/expressions_explorer/*"],
|
||||||
"@kbn/field-formats-example-plugin": ["examples/field_formats_example"],
|
"@kbn/field-formats-example-plugin": ["examples/field_formats_example"],
|
||||||
"@kbn/field-formats-example-plugin/*": ["examples/field_formats_example/*"],
|
"@kbn/field-formats-example-plugin/*": ["examples/field_formats_example/*"],
|
||||||
|
"@kbn/guided-onboarding-example-plugin": ["examples/guided_onboarding_example"],
|
||||||
|
"@kbn/guided-onboarding-example-plugin/*": ["examples/guided_onboarding_example/*"],
|
||||||
"@kbn/hello-world-plugin": ["examples/hello_world"],
|
"@kbn/hello-world-plugin": ["examples/hello_world"],
|
||||||
"@kbn/hello-world-plugin/*": ["examples/hello_world/*"],
|
"@kbn/hello-world-plugin/*": ["examples/hello_world/*"],
|
||||||
"@kbn/locator-examples-plugin": ["examples/locator_examples"],
|
"@kbn/locator-examples-plugin": ["examples/locator_examples"],
|
||||||
|
@ -113,6 +115,8 @@
|
||||||
"@kbn/expressions-plugin/*": ["src/plugins/expressions/*"],
|
"@kbn/expressions-plugin/*": ["src/plugins/expressions/*"],
|
||||||
"@kbn/field-formats-plugin": ["src/plugins/field_formats"],
|
"@kbn/field-formats-plugin": ["src/plugins/field_formats"],
|
||||||
"@kbn/field-formats-plugin/*": ["src/plugins/field_formats/*"],
|
"@kbn/field-formats-plugin/*": ["src/plugins/field_formats/*"],
|
||||||
|
"@kbn/guided-onboarding-plugin": ["src/plugins/guided_onboarding"],
|
||||||
|
"@kbn/guided-onboarding-plugin/*": ["src/plugins/guided_onboarding/*"],
|
||||||
"@kbn/home-plugin": ["src/plugins/home"],
|
"@kbn/home-plugin": ["src/plugins/home"],
|
||||||
"@kbn/home-plugin/*": ["src/plugins/home/*"],
|
"@kbn/home-plugin/*": ["src/plugins/home/*"],
|
||||||
"@kbn/input-control-vis-plugin": ["src/plugins/input_control_vis"],
|
"@kbn/input-control-vis-plugin": ["src/plugins/input_control_vis"],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue