Skip to content

Commit

Permalink
Merge branch 'main' into 154841-remove-legacy-field-stats
Browse files Browse the repository at this point in the history
  • Loading branch information
jughosta authored Apr 21, 2023
2 parents 580e64b + bc3471c commit 9385108
Show file tree
Hide file tree
Showing 27 changed files with 450 additions and 119 deletions.
8 changes: 8 additions & 0 deletions examples/guided_onboarding_example/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/publi
import { StepTwo } from './step_two';
import { StepOne } from './step_one';
import { StepThree } from './step_three';
import { StepFour } from './step_four';
import { Main } from './main';

interface GuidedOnboardingExampleAppDeps {
Expand Down Expand Up @@ -65,6 +66,13 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps
<Route exact path="/stepThree">
<StepThree guidedOnboarding={guidedOnboarding} />
</Route>
p
<Route
path="/stepFour/:indexName?"
render={(routeProps) => (
<StepFour guidedOnboarding={guidedOnboarding} {...routeProps} />
)}
/>
</Switch>
</Router>
</EuiPageContent>
Expand Down
8 changes: 8 additions & 0 deletions examples/guided_onboarding_example/public/components/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ export const Main = (props: MainProps) => {
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={() => history.push('stepFour')}>
<FormattedMessage
id="guidedOnboardingExample.main.examplePages.stepFour.link"
defaultMessage="Step 4"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContentBody>
</>
Expand Down
83 changes: 83 additions & 0 deletions examples/guided_onboarding_example/public/components/step_four.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 } from '@elastic/eui';

import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
EuiPageContentBody_Deprecated as EuiPageContentBody,
EuiCode,
} from '@elastic/eui';
import { RouteComponentProps } from 'react-router-dom';

interface StepFourProps {
guidedOnboarding: GuidedOnboardingPluginStart;
}

export const StepFour = (props: StepFourProps & RouteComponentProps<{ indexName: string }>) => {
const {
guidedOnboarding: { guidedOnboardingApi },
match: {
params: { indexName },
},
} = props;

const [, setIsTourStepOpen] = useState<boolean>(false);

useEffect(() => {
const subscription = guidedOnboardingApi
?.isGuideStepActive$('testGuide', 'step4')
.subscribe((isStepActive) => {
setIsTourStepOpen(isStepActive);
});
return () => subscription?.unsubscribe();
}, [guidedOnboardingApi]);

return (
<>
<EuiPageContentHeader>
<EuiTitle>
<h2>
<FormattedMessage
id="guidedOnboardingExample.stepFour.title"
defaultMessage="Example step 4"
/>
</h2>
</EuiTitle>
</EuiPageContentHeader>
<EuiPageContentBody>
<EuiText>
<p>
<FormattedMessage
id="guidedOnboardingExample.guidesSelection.stepFour.explanation"
defaultMessage="This step has a dynamic URL with a param {indexName} passed in step 1"
values={{
indexName: (
<EuiCode language="javascript">&#123;indexName: {indexName}&#125;</EuiCode>
),
}}
/>
</p>
</EuiText>
<EuiSpacer />

<EuiButton
onClick={async () => {
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step4');
}}
>
Complete step 4
</EuiButton>
</EuiPageContentBody>
</>
);
};
79 changes: 57 additions & 22 deletions examples/guided_onboarding_example/public/components/step_one.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import {
EuiPageContentHeader_Deprecated as EuiPageContentHeader,
EuiPageContentBody_Deprecated as EuiPageContentBody,
EuiSpacer,
EuiCode,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
} from '@elastic/eui';

import useObservable from 'react-use/lib/useObservable';
Expand All @@ -30,6 +35,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
const { guidedOnboardingApi } = guidedOnboarding;

const [isTourStepOpen, setIsTourStepOpen] = useState<boolean>(false);
const [indexName, setIndexName] = useState('test1234');

const isTourActive = useObservable(
guidedOnboardingApi!.isGuideStepActive$('testGuide', 'step1'),
Expand Down Expand Up @@ -59,30 +65,59 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
Test guide, step 1, a EUI tour will be displayed, pointing to the button below."
/>
</p>
<p>
<FormattedMessage
id="guidedOnboardingExample.guidesSelection.stepOne.dynamicParamsExplanation"
defaultMessage="There is also an input field to provide a dynamic parameter {indexName} for step 4."
values={{
indexName: <EuiCode language="javascript">indexName</EuiCode>,
}}
/>
</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 1"
anchorPosition="rightUp"
>
<EuiButton
onClick={async () => {
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step1');
}}
>
Complete step 1
</EuiButton>
</EuiTourStep>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label={
<FormattedMessage
id="guidedOnboardingExample.guidesSelection.stepOne.indexNameInputLabel"
defaultMessage="indexName"
/>
}
>
<EuiFieldText value={indexName} onChange={(e) => setIndexName(e.target.value)} />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow hasEmptyLabelSpace>
<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 1"
anchorPosition="rightUp"
>
<EuiButton
onClick={async () => {
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step1', {
indexName,
});
}}
>
Complete step 1
</EuiButton>
</EuiTourStep>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContentBody>
</>
);
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-guided-onboarding/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {
GuideConfig,
StepConfig,
StepDescriptionWithLink,
GuideParams,
} from './src/types';
export { GuideCards, GuideFilters } from './src/components/landing_page';
export type { GuideFilterValues } from './src/components/landing_page';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,14 @@ export const testGuideConfig: GuideConfig = {
path: 'stepThree',
},
},
{
id: 'step4',
title: 'Step 4 (dynamic url)',
description: 'This step navigates to a dynamic URL with a param indexName passed in step 1.',
location: {
appID: 'guidedOnboardingExample',
path: 'stepFour/{indexName}',
},
},
],
};
15 changes: 14 additions & 1 deletion packages/kbn-guided-onboarding/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ export type GuideId =
type KubernetesStepIds = 'add_data' | 'view_dashboard' | 'tour_observability';
type SiemStepIds = 'add_data' | 'rules' | 'alertsCases';
type SearchStepIds = 'add_data' | 'search_experience';
type TestGuideIds = 'step1' | 'step2' | 'step3';
type TestGuideIds = 'step1' | 'step2' | 'step3' | 'step4';

export type GuideStepIds = KubernetesStepIds | SiemStepIds | SearchStepIds | TestGuideIds;

export type GuideParams = Record<string, string>;

export interface GuideState {
guideId: GuideId;
status: GuideStatus;
isActive?: boolean; // Drives the current guide shown in the dropdown panel
steps: GuideStep[];
params?: GuideParams;
}

/**
Expand Down Expand Up @@ -92,6 +95,16 @@ export interface StepConfig {
description?: string | StepDescriptionWithLink;
// description list is displayed as an unordered list, can be combined with description
descriptionList?: Array<string | StepDescriptionWithLink>;
/*
* Kibana location where the user will be redirected when starting or continuing a guide step.
* The property `path` can use dynamic parameters, for example `testPath/{indexID}/{pageID}.
* For the dynamic path to be configured correctly, the values of the parameters need to be passed to
* the api service when completing one of the previous steps.
* For example, if step 2 has a dynamic parameter `indexID` in its location path
* { appID: 'test', path: 'testPath/{indexID}', params: ['indexID'] },
* its value needs to be passed to the api service when completing step 1. For example,
* `guidedOnboardingAPI.completeGuideStep('testGuide', 'step1', { indexID: 'testIndex' })
*/
location?: {
appID: string;
path: string;
Expand Down
26 changes: 19 additions & 7 deletions src/plugins/guided_onboarding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ When starting Kibana with `yarn start --run-examples` the `guided_onboarding_exa
The guided onboarding plugin exposes an API service from its start contract that is intended to be used by other plugins. The API service allows consumers to access the current state of the guided onboarding process and manipulate it.

To use the API service in your plugin, declare the guided onboarding plugin as a dependency in the file `kibana.json` of your plugin. Add the API service to your plugin's start dependencies to rely on the provided TypeScript interface:
```
```js
export interface AppPluginStartDependencies {
guidedOnboarding: GuidedOnboardingPluginStart;
}
```
The API service is now available to your plugin in the setup lifecycle function of your plugin
```
```js
// startDependencies is of type AppPluginStartDependencies
const [coreStart, startDependencies] = await core.getStartServices();
```
or in the start lifecycle function of your plugin.
```
```js
public start(core: CoreStart, startDependencies: AppPluginStartDependencies) {
...
}
Expand All @@ -67,7 +67,7 @@ public start(core: CoreStart, startDependencies: AppPluginStartDependencies) {

The API service exposes an Observable that contains a boolean value for the state of a specific guide step. For example, if your plugin needs to check if the "Add data" step of the SIEM guide is currently active, you could use the following code snippet.

```
```js
const { guidedOnboardingApi } = guidedOnboarding;
const isDataStepActive = useObservable(guidedOnboardingApi!.isGuideStepActive$('siem', 'add_data'));
useEffect(() => {
Expand All @@ -76,7 +76,7 @@ useEffect(() => {
```

Alternatively, you can subscribe to the Observable directly.
```
```js
useEffect(() => {
const subscription = guidedOnboardingApi?.isGuideStepActive$('siem', 'add_data').subscribe((isDataStepACtive) => {
// do some logic depending on the step state
Expand All @@ -89,7 +89,7 @@ useEffect(() => {
Similar to `isGuideStepActive$`, the observable `isGuideStepReadyToComplete$` can be used to track the state of a step that is configured for manual completion. The observable broadcasts `true` when the manual completion popover is displayed and the user can mark the step "done". In this state the step is not in progress anymore but is not yet fully completed.
### completeGuideStep(guideId: GuideId, stepId: GuideStepIds): Promise\<{ pluginState: PluginState } | undefined\>
### completeGuideStep(guideId: GuideId, stepId: GuideStepIds, params?: GuideParams): Promise\<{ pluginState: PluginState } | undefined\>
The API service exposes an async function to mark a guide step as completed.
If the specified guide step is not currently active, the function is a noop. In that case the return value is `undefined`,
otherwise an updated `PluginState` is returned.
Expand All @@ -98,8 +98,20 @@ otherwise an updated `PluginState` is returned.
await guidedOnboardingApi?.completeGuideStep('siem', 'add_data');
```
The function also accepts an optional argument `params` that will be saved in the state and later used for step URLs with dynamic parameters. For example, step 2 of the guide has a dynamic parameter `indexID` in its location path:
```js
const step2Config = {
id: 'step2',
description: 'Step with dynamic url',
location: {
appID: 'test', path: 'testPath/{indexID}'
}
};
```
The value of the parameter `indexID` needs to be passed to the API service when completing step 1: `completeGuideStep('testGuide', 'step1', { indexID: 'testIndex' })`
## Guides config
To use the API service, you need to know a guide ID (currently one of `search`, `kubernetes`, `siem`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). The consumers of guided onboarding register their guide configs themselves and have therefore full control over the guide ID and step IDs used for their guide. For more details on registering a guide config, see below.
To use the API service, you need to know a guide ID (currently one of `appSearch`, `websiteSearch`, `databaseSearch`, `kubernetes`, `siem`) and a step ID (for example, `add_data`, `search_experience`, `rules` etc). The consumers of guided onboarding register their guide configs themselves and have therefore full control over the guide ID and step IDs used for their guide. For more details on registering a guide config, see below.
## Server side: register a guide config
The guided onboarding exposes a function `registerGuideConfig(guideId: GuideId, guideConfig: GuideConfig)` function in its setup contract. This function allows consumers to register a guide config for a specified guide ID. The function throws an error if a config already exists for the guide ID. See code examples in following plugins:
Expand Down
Loading

0 comments on commit 9385108

Please sign in to comment.