Disable guided onboarding on serverless (#168303)

This commit is contained in:
Alison Goryachev 2023-11-03 13:11:05 -04:00 committed by GitHub
parent ef67add16c
commit 2b1cd4d080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 209 additions and 156 deletions

View file

@ -51,9 +51,12 @@ xpack.index_management.enableDataStreamsStorageColumn: false
dev_tools.deeplinks.navLinkStatus: visible
management.deeplinks.navLinkStatus: visible
# Onboarding team UI configurations
xpack.cloud_integrations.data_migration.enabled: false
guided_onboarding.enabled: false
# Other disabled plugins
xpack.canvas.enabled: false
xpack.cloud_integrations.data_migration.enabled: false
data.search.sessions.enabled: false
advanced_settings.enabled: false

View file

@ -8,7 +8,9 @@
"server": true,
"browser": true,
"requiredPlugins": [
"navigation",
"navigation"
],
"optionalPlugins": [
"guidedOnboarding"
]
}

View file

@ -39,7 +39,7 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps
/>
}
/>
{guidedOnboarding.guidedOnboardingApi?.isEnabled ? (
{guidedOnboarding?.guidedOnboardingApi?.isEnabled ? (
<EuiPageTemplate.Section>
<Router history={history}>
<Routes>

View file

@ -31,7 +31,7 @@ import type { GuideState, GuideStepIds, GuideId, GuideStep } from '@kbn/guided-o
import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
interface MainProps {
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
notifications: CoreStart['notifications'];
}
@ -48,10 +48,7 @@ const selectOptions: EuiSelectOption[] = exampleGuideIds.map((guideId) => ({
text: guideId,
}));
export const Main = (props: MainProps) => {
const {
guidedOnboarding: { guidedOnboardingApi },
notifications,
} = props;
const { guidedOnboarding, notifications } = props;
const history = useHistory();
const [guidesState, setGuidesState] = useState<GuideState[] | undefined>(undefined);
const [activeGuide, setActiveGuide] = useState<GuideState | undefined>(undefined);
@ -61,12 +58,12 @@ export const Main = (props: MainProps) => {
useEffect(() => {
const fetchGuidesState = async () => {
const newGuidesState = await guidedOnboardingApi?.fetchAllGuidesState();
const newGuidesState = await guidedOnboarding?.guidedOnboardingApi?.fetchAllGuidesState();
setGuidesState(newGuidesState ? newGuidesState.state : []);
};
fetchGuidesState();
}, [guidedOnboardingApi]);
}, [guidedOnboarding]);
useEffect(() => {
const newActiveGuide = guidesState?.find((guide) => guide.isActive === true);
@ -76,7 +73,10 @@ export const Main = (props: MainProps) => {
}, [guidesState, setActiveGuide]);
const activateGuide = async (guideId: GuideId, guideState?: GuideState) => {
const response = await guidedOnboardingApi?.activateGuide(guideId, guideState);
const response = await guidedOnboarding?.guidedOnboardingApi?.activateGuide(
guideId,
guideState
);
if (response) {
notifications.toasts.addSuccess(
@ -92,7 +92,9 @@ export const Main = (props: MainProps) => {
return;
}
const selectedGuideConfig = await guidedOnboardingApi?.getGuideConfig(selectedGuide);
const selectedGuideConfig = await guidedOnboarding?.guidedOnboardingApi?.getGuideConfig(
selectedGuide
);
if (!selectedGuideConfig) {
return;
@ -134,7 +136,7 @@ export const Main = (props: MainProps) => {
guideId: selectedGuide!,
};
const response = await guidedOnboardingApi?.updatePluginState(
const response = await guidedOnboarding?.guidedOnboardingApi?.updatePluginState(
{ status: 'in_progress', guide: updatedGuideState },
true
);
@ -170,7 +172,7 @@ export const Main = (props: MainProps) => {
<p>
<FormattedMessage
id="guidedOnboardingExample.guidesSelection.state.explanation"
defaultMessage="The guide state on this page is updated automatically via an Observable,
defaultMessage="The guide state on this page is updated automatically via an Observable subscription,
so there is no need to 'load' the state from the server."
/>
</p>

View file

@ -16,24 +16,22 @@ import { EuiPageHeader, EuiPageSection, EuiCode } from '@elastic/eui';
import { useParams } from 'react-router-dom';
interface StepFourProps {
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
}
export const StepFour: React.FC<StepFourProps> = ({
guidedOnboarding: { guidedOnboardingApi },
}) => {
export const StepFour: React.FC<StepFourProps> = ({ guidedOnboarding }) => {
const { indexName } = useParams<{ indexName: string }>();
const [, setIsTourStepOpen] = useState<boolean>(false);
useEffect(() => {
const subscription = guidedOnboardingApi
const subscription = guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$('testGuide', 'step4')
.subscribe((isStepActive) => {
setIsTourStepOpen(isStepActive);
});
return () => subscription?.unsubscribe();
}, [guidedOnboardingApi]);
}, [guidedOnboarding]);
return (
<>
@ -65,7 +63,7 @@ export const StepFour: React.FC<StepFourProps> = ({
<EuiButton
onClick={async () => {
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step4');
await guidedOnboarding?.guidedOnboardingApi?.completeGuideStep('testGuide', 'step4');
}}
>
Complete step 4

View file

@ -23,27 +23,25 @@ import {
EuiFormRow,
} from '@elastic/eui';
import useObservable from 'react-use/lib/useObservable';
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types';
interface GuidedOnboardingExampleAppDeps {
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
}
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'),
false
);
useEffect(() => {
setIsTourStepOpen(isTourActive);
}, [isTourActive]);
const subscription = guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$('testGuide', 'step1')
.subscribe((isStepActive) => {
setIsTourStepOpen(isStepActive);
});
return () => subscription?.unsubscribe();
}, [guidedOnboarding]);
return (
<>
<EuiPageHeader>
@ -107,9 +105,13 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
>
<EuiButton
onClick={async () => {
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step1', {
indexName,
});
await guidedOnboarding?.guidedOnboardingApi?.completeGuideStep(
'testGuide',
'step1',
{
indexName,
}
);
}}
>
Complete step 1

View file

@ -11,7 +11,7 @@ import { PluginInitializerContext, CoreSetup, Plugin, Logger } from '@kbn/core/s
import { testGuideId, testGuideConfig } from '@kbn/guided-onboarding';
interface PluginsSetup {
guidedOnboarding: GuidedOnboardingPluginSetup;
guidedOnboarding?: GuidedOnboardingPluginSetup;
}
export class GuidedOnboardingExamplePlugin implements Plugin {
@ -23,7 +23,7 @@ export class GuidedOnboardingExamplePlugin implements Plugin {
public setup(coreSetup: CoreSetup, { guidedOnboarding }: PluginsSetup) {
this.logger.debug('guidedOnboardingExample: Setup');
guidedOnboarding.registerGuideConfig(testGuideId, testGuideConfig);
guidedOnboarding?.registerGuideConfig(testGuideId, testGuideConfig);
return {};
}

View file

@ -1,14 +1,15 @@
# Guided Onboarding
This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages.
This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages.
The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API.
The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API.
The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins that need to react to the guided onboarding state, for example hide or display UI elements if a guide step is active.
Besides the internal API routes, the server-side code also exposes a function to register guide configs from the server-side setup start contract. This function is intended for external use by any plugin that need to add a new guide or modify an existing one.
Besides the internal API routes, the server-side code also exposes a function to register guide configs from the server-side setup start contract. This function is intended for external use by any plugin that need to add a new guide or modify an existing one.
---
## Current functionality
The solution plugins register their config that specifies the title, the description and the steps of a guide. The config is fetched via an http request to a guided onboarding endpoint. This endpoint should only be used internally by the guided onboarding API service. The configs are basically just `js` objects that are loaded into the Kibana server memory on startup. Because configs are not expected to be changed by the user, we dont need to use saved objects.
@ -20,6 +21,7 @@ A step is completed on the solution page code by calling a function in the guide
The plugins state keeps track of which guide has been started and its current progress. The state also includes the information if the user has started any guide, has completed any guide or if they skipped the guided onboarding, or if they quit the guide before completion. We also store the date when the user first looked at the landing page and if they haven't started any guide, the header button is displayed for the first 30 days. When clicked, the button redirects the user back to the landing page to start a guide.
## Architecture description
The guided onboarding is currently implemented in a separate `guided_onboarding` plugin that contains the code for the header button ([link](https://github.com/elastic/kibana/blob/main/src/plugins/guided_onboarding/public/components/guide_button.tsx)), the dropdown panel ([link](https://github.com/elastic/kibana/blob/main/src/plugins/guided_onboarding/public/components/guide_panel.tsx)) and the API service ([link](https://github.com/elastic/kibana/blob/main/src/plugins/guided_onboarding/public/services/api.service.ts)) exposed out of the client side that can be used by other plugins to get/update the state of the guided onboarding.
For example, when a user goes through the SIEM guide they are first taken to the integrations page where they follow some EUI tour steps and install the Elastic Agent and the Elastic Defend integration. The code on the integrations page uses the guided onboarding API service to check if a guide for the Elastic Defend integration is currently in progress. If yes, the page will display the EUI tour steps to guide the user. The page will also use the API service to update the guided onboarding state to the next step when the user completes the installation.
@ -35,27 +37,33 @@ When starting Kibana with `yarn start --run-examples` the `guided_onboarding_exa
1. Guided onboarding is only enabled on cloud. Update your `kibana.dev.yml` file with `xpack.cloud.id: 'testID'` to imitate the Cloud environment.
2. Start Kibana with the example plugins enabled: `yarn start --run-examples`.
2. Start Kibana with the example plugins enabled: `yarn start --run-examples`.
3. Navigate to `/app/home#/getting_started` to view the onboarding landing page and start a guide. Alternatively, you can also start a guide within the guided onboarding example plugin at `/app/guidedOnboardingExample`. The example plugin includes a sample guide that showcases the framework's capabilities. It also provides a form to dynamically start a guide at a specific step.
## Client side: API service
*Also see `KIBANA_FOLDER/examples/guided_onboarding_example` for code examples.*
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.
_Also see `KIBANA_FOLDER/examples/guided_onboarding_example` for code examples._
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;
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) {
...
@ -63,35 +71,30 @@ public start(core: CoreStart, startDependencies: AppPluginStartDependencies) {
```
### isGuideStepActive$(guideId: GuideId, stepId: GuideStepIds): Observable\<boolean\>
*Also see `KIBANA_FOLDER/examples/guided_onboarding_example/public/components/step_one.tsx`.*
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.
_Also see `KIBANA_FOLDER/examples/guided_onboarding_example/public/components/step_one.tsx`._
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(() => {
// do some logic depending on the step state
}, [isDataStepActive]);
```
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
const subscription = guidedOnboardingApi
?.isGuideStepActive$('siem', 'add_data')
.subscribe((isDataStepActive) => {
// do some logic depending on the step state
});
return () => subscription?.unsubscribe();
return () => subscription?.unsubscribe();
}, [guidedOnboardingApi]);
```
### isGuideStepReadyToComplete$(guideId: GuideId, stepId: GuideStepIds): Observable\<boolean\>
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.
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, 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`,
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.
```
@ -99,34 +102,39 @@ 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}'
}
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' })`
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 `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.
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:
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:
- enterprise search: `x-pack/plugins/enterprise_search/server/plugin.ts`
- observability: `x-pack/plugins/observability/server/plugin.ts`
- security solution: `x-pack/plugins/security_solution/server/plugin.ts`
## Adding a new guide
Follow these simple steps to add a new guide to the guided onboarding framework. For more detailed information about framework functionality and architecture and about API services exposed by the plugin, please read the full readme.
1. Declare the `guidedOnboarding` plugin as a dependency in your plugin's `kibana.json` file. Add the guided onboarding plugin's client-side start contract to your plugin's client-side start dependencies and the guided onboarding plugin's server-side setup contract to your plugin's server-side dependencies.
1. Declare the `guidedOnboarding` plugin as an optional dependency in your plugin's `kibana.json` file. Add the guided onboarding plugin's client-side start contract to your plugin's client-side start dependencies and the guided onboarding plugin's server-side setup contract to your plugin's server-side dependencies.
2. Define the configuration for your guide. At a high level, this includes a title, description, and list of steps. See this [example config](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/common/test_guide_config.ts) or consult the `GuideConfig` interface.
3. Register your guide during your plugin's server-side setup by calling a function exposed by the guided onboarding plugin: `registerGuideConfig(guideId: GuideId, guideConfig: GuideConfig)`. For an example, see this [example plugin](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/server/plugin.ts).
4. Update the cards on the landing page to include your guide in the use case selection. Make sure that the card doesn't have the property `navigateTo` because that is only used for cards that redirect to Kibana pages and don't start a guide. Also add the same value to the property `guideId` as used in the guide config. Landing page cards are configured in this [kbn-guided-onboarding package](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.tsx).
5. Integrate the new guide into your Kibana pages by using the guided onboarding client-side API service. Make sure your Kibana pages correctly display UI elements depending on the active guide step and the UI flow is straight forward to complete the guide. See existing guides for an example and read more about the API service in this file, the section "Client-side: API service".
6. Optionally, update the example plugin's [form](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/public/components/main.tsx#L38) to be able to start your guide from that page and activate any step in your guide (useful to test your guide steps).
3. Register your guide during your plugin's server-side setup by calling a function exposed by the guided onboarding plugin: `registerGuideConfig(guideId: GuideId, guideConfig: GuideConfig)`. For an example, see this [example plugin](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/server/plugin.ts).
4. Update the cards on the landing page to include your guide in the use case selection. Make sure that the card doesn't have the property `navigateTo` because that is only used for cards that redirect to Kibana pages and don't start a guide. Also add the same value to the property `guideId` as used in the guide config. Landing page cards are configured in this [kbn-guided-onboarding package](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.tsx).
5. Integrate the new guide into your Kibana pages by using the guided onboarding client-side API service. Make sure your Kibana pages correctly display UI elements depending on the active guide step and the UI flow is straight forward to complete the guide. See existing guides for an example and read more about the API service in this file, the section "Client-side: API service".
6. Optionally, update the example plugin's [form](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/public/components/main.tsx#L38) to be able to start your guide from that page and activate any step in your guide (useful to test your guide steps).

View file

@ -0,0 +1,24 @@
/*
* 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 { offeringBasedSchema, schema, TypeOf } from '@kbn/config-schema';
import { PluginConfigDescriptor } from '@kbn/core-plugins-server';
const configSchema = schema.object({
enabled: offeringBasedSchema({
// guided_onboarding is disabled in serverless; refer to the serverless.yml file as the source of truth
// We take this approach in order to have a central place (serverless.yml) to view disabled plugins across Kibana
serverless: schema.boolean({ defaultValue: true }),
}),
});
export type GuidedOnboardingConfig = TypeOf<typeof configSchema>;
export const config: PluginConfigDescriptor<GuidedOnboardingConfig> = {
schema: configSchema,
};

View file

@ -14,3 +14,5 @@ export function plugin(initializerContext: PluginInitializerContext) {
}
export type { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types';
export { config } from './config';

View file

@ -13,6 +13,8 @@
"@kbn/i18n-react",
"@kbn/core-application-browser-mocks",
"@kbn/core-notifications-browser-mocks",
"@kbn/config-schema",
"@kbn/core-plugins-server",
"@kbn/test-jest-helpers",
"@kbn/i18n",
"@kbn/core-http-browser",

View file

@ -71,7 +71,7 @@ export function HomeApp({ directories, solutions }) {
<Routes>
<Route path="/tutorial/:id" render={renderTutorial} />
<Route path="/tutorial_directory/:tab?" render={renderTutorialDirectory} />
{guidedOnboardingService.isEnabled && (
{guidedOnboardingService?.isEnabled && (
<Route path="/getting_started">
<GettingStarted />
</Route>

View file

@ -41,7 +41,7 @@ import {
export interface HomePluginStartDependencies {
dataViews: DataViewsPublicPluginStart;
urlForwarding: UrlForwardingStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
}
export interface HomePluginSetupDependencies {
@ -104,7 +104,7 @@ export class HomePublicPlugin
addDataService: this.addDataService,
featureCatalogue: this.featuresCatalogueRegistry,
welcomeService: this.welcomeService,
guidedOnboardingService: guidedOnboarding.guidedOnboardingApi,
guidedOnboardingService: guidedOnboarding?.guidedOnboardingApi,
cloud,
});
coreStart.chrome.docTitle.change(

View file

@ -22,7 +22,7 @@ export interface KibanaDeps {
data: DataPublicPluginStart;
discover: DiscoverStart;
features: FeaturesPluginStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
licensing: LicensingPluginStart;
security: SecurityPluginStart;
share: SharePluginStart;

View file

@ -20,7 +20,6 @@
"logsShared",
"cloud",
"esUiShared",
"guidedOnboarding",
"lens",
"embeddable",
"share"
@ -31,7 +30,8 @@
"home",
"ml",
"spaces",
"usageCollection"
"usageCollection",
"guidedOnboarding",
],
"requiredBundles": [
"kibanaReact"

View file

@ -11,8 +11,6 @@ import { useParams } from 'react-router-dom';
import { useValues } from 'kea';
import useObservable from 'react-use/lib/useObservable';
import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -80,24 +78,39 @@ export const SearchIndex: React.FC = () => {
productAccess: { hasAppSearchAccess },
productFeatures: { hasDefaultIngestPipeline },
} = useValues(KibanaLogic);
const isAppGuideActive = useObservable(
guidedOnboarding.guidedOnboardingApi!.isGuideStepActive$('appSearch', 'add_data')
);
const isWebsiteGuideActive = useObservable(
guidedOnboarding.guidedOnboardingApi!.isGuideStepActive$('websiteSearch', 'add_data')
);
const isDatabaseGuideActive = useObservable(
guidedOnboarding.guidedOnboardingApi!.isGuideStepActive$('databaseSearch', 'add_data')
);
useEffect(() => {
if (isAppGuideActive && index?.count) {
guidedOnboarding.guidedOnboardingApi?.completeGuideStep('appSearch', 'add_data');
} else if (isWebsiteGuideActive && index?.count) {
guidedOnboarding.guidedOnboardingApi?.completeGuideStep('websiteSearch', 'add_data');
} else if (isDatabaseGuideActive && index?.count) {
guidedOnboarding.guidedOnboardingApi?.completeGuideStep('databaseSearch', 'add_data');
}
}, [isAppGuideActive, isWebsiteGuideActive, isDatabaseGuideActive, index?.count]);
const subscription = guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$('appSearch', 'add_data')
.subscribe((isStepActive) => {
if (isStepActive && index?.count) {
guidedOnboarding?.guidedOnboardingApi?.completeGuideStep('appSearch', 'add_data');
}
});
return () => subscription?.unsubscribe();
}, [guidedOnboarding, index?.count]);
useEffect(() => {
const subscription = guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$('websiteSearch', 'add_data')
.subscribe((isStepActive) => {
if (isStepActive && index?.count) {
guidedOnboarding?.guidedOnboardingApi?.completeGuideStep('websiteSearch', 'add_data');
}
});
return () => subscription?.unsubscribe();
}, [guidedOnboarding, index?.count]);
useEffect(() => {
const subscription = guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$('databaseSearch', 'add_data')
.subscribe((isStepActive) => {
if (isStepActive && index?.count) {
guidedOnboarding.guidedOnboardingApi?.completeGuideStep('databaseSearch', 'add_data');
}
});
return () => subscription?.unsubscribe();
}, [guidedOnboarding, index?.count]);
const ALL_INDICES_TABS: EuiTabbedContentTab[] = [
{

View file

@ -40,7 +40,7 @@ export interface KibanaLogicProps {
cloud?: CloudSetup;
config: ClientConfigType;
data: DataPublicPluginStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
history: ScopedHistory;
isSidebarEnabled: boolean;
lens: LensPublicStart;

View file

@ -87,7 +87,7 @@ interface PluginsSetup {
customIntegrations?: CustomIntegrationsPluginSetup;
features: FeaturesPluginSetup;
globalSearch: GlobalSearchPluginSetup;
guidedOnboarding: GuidedOnboardingPluginSetup;
guidedOnboarding?: GuidedOnboardingPluginSetup;
logsShared: LogsSharedPluginSetup;
ml?: MlPluginSetup;
security: SecurityPluginSetup;
@ -294,13 +294,13 @@ export class EnterpriseSearchPlugin implements Plugin {
* Register a config for the search guide
*/
if (config.canDeployEntSearch) {
guidedOnboarding.registerGuideConfig(appSearchGuideId, appSearchGuideConfig);
guidedOnboarding?.registerGuideConfig(appSearchGuideId, appSearchGuideConfig);
}
if (config.hasWebCrawler) {
guidedOnboarding.registerGuideConfig(websiteSearchGuideId, websiteSearchGuideConfig);
guidedOnboarding?.registerGuideConfig(websiteSearchGuideId, websiteSearchGuideConfig);
}
if (config.hasConnectors) {
guidedOnboarding.registerGuideConfig(databaseSearchGuideId, databaseSearchGuideConfig);
guidedOnboarding?.registerGuideConfig(databaseSearchGuideId, databaseSearchGuideConfig);
}
/**

View file

@ -15,7 +15,6 @@
"dataViews",
"features",
"files",
"guidedOnboarding",
"inspector",
"lens",
"observabilityShared",

View file

@ -33,7 +33,6 @@ import {
} from '@kbn/triggers-actions-ui-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
@ -60,7 +59,6 @@ export interface ExploratoryViewPublicPluginsStart {
discover: DiscoverStart;
embeddable: EmbeddableStart;
guidedOnboarding: GuidedOnboardingPluginStart;
lens: LensPublicStart;
licensing: LicensingPluginStart;
observabilityShared: ObservabilitySharedPluginStart;

View file

@ -16,7 +16,6 @@
"@kbn/lens-plugin",
"@kbn/spaces-plugin",
"@kbn/unified-search-plugin",
"@kbn/guided-onboarding-plugin",
"@kbn/discover-plugin",
"@kbn/i18n",
"@kbn/data-views-plugin",

View file

@ -21,7 +21,6 @@
"unifiedSearch",
"savedObjectsTagging",
"taskManager",
"guidedOnboarding",
"files",
"uiActions",
"dashboard"
@ -36,6 +35,7 @@
"discover",
"ingestPipelines",
"spaces",
"guidedOnboarding",
],
"requiredBundles": [
"kibanaReact",

View file

@ -143,7 +143,7 @@ export const ConfirmIncomingDataWithPreview: React.FunctionComponent<Props> = ({
if (!isLoading && enrolledAgents > 0 && numAgentsWithData > 0) {
setAgentDataConfirmed(true);
if (isGuidedOnboardingActive) {
guidedOnboarding.guidedOnboardingApi?.completeGuidedOnboardingForIntegration(
guidedOnboarding?.guidedOnboardingApi?.completeGuidedOnboardingForIntegration(
packageInfo?.name
);
}

View file

@ -42,7 +42,7 @@ export const ConfirmIncomingData: React.FunctionComponent<Props> = ({
if (!isLoading && enrolledAgents > 0 && numAgentsWithData > 0) {
setAgentDataConfirmed(true);
if (installedPolicy?.name && isGuidedOnboardingActive) {
guidedOnboarding.guidedOnboardingApi?.completeGuidedOnboardingForIntegration(
guidedOnboarding?.guidedOnboardingApi?.completeGuidedOnboardingForIntegration(
installedPolicy!.name
);
}

View file

@ -17,7 +17,7 @@ export const useIsGuidedOnboardingActive = (packageName?: string): boolean => {
const { guidedOnboarding } = useStartServices();
const isGuidedOnboardingActiveForIntegration = useObservable(
// if guided onboarding is not available, return false
guidedOnboarding.guidedOnboardingApi
guidedOnboarding?.guidedOnboardingApi
? guidedOnboarding.guidedOnboardingApi.isGuidedOnboardingActiveForIntegration$(packageName)
: of(false)
);

View file

@ -129,7 +129,7 @@ export interface FleetStartDeps {
share: SharePluginStart;
cloud?: CloudStart;
usageCollection?: UsageCollectionStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
}
export interface FleetStartServices extends CoreStart, Exclude<FleetStartDeps, 'cloud'> {
@ -140,7 +140,7 @@ export interface FleetStartServices extends CoreStart, Exclude<FleetStartDeps, '
discover?: DiscoverStart;
spaces?: SpacesPluginStart;
authz: FleetAuthz;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
}
export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDeps, FleetStartDeps> {

View file

@ -23,7 +23,6 @@
"exploratoryView",
"features",
"files",
"guidedOnboarding",
"inspector",
"lens",
"observabilityShared",
@ -44,7 +43,8 @@
"usageCollection",
"cloud",
"spaces",
"serverless"
"serverless",
"guidedOnboarding"
],
"requiredBundles": [
"data",
@ -58,4 +58,4 @@
"common"
]
}
}
}

View file

@ -127,7 +127,7 @@ export interface ObservabilityPublicPluginsStart {
discover: DiscoverStart;
embeddable: EmbeddableStart;
exploratoryView: ExploratoryViewPublicStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
lens: LensPublicStart;
licensing: LicensingPluginStart;
observabilityShared: ObservabilitySharedPluginStart;

View file

@ -63,7 +63,7 @@ export type ObservabilityPluginSetup = ReturnType<ObservabilityPlugin['setup']>;
interface PluginSetup {
alerting: PluginSetupContract;
features: FeaturesSetup;
guidedOnboarding: GuidedOnboardingPluginSetup;
guidedOnboarding?: GuidedOnboardingPluginSetup;
ruleRegistry: RuleRegistryPluginSetupContract;
share: SharePluginSetup;
spaces?: SpacesPluginSetup;
@ -344,7 +344,7 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
/**
* Register a config for the observability guide
*/
plugins.guidedOnboarding.registerGuideConfig(kubernetesGuideId, kubernetesGuideConfig);
plugins.guidedOnboarding?.registerGuideConfig(kubernetesGuideId, kubernetesGuideConfig);
return {
getAlertDetailsConfig() {

View file

@ -7,8 +7,8 @@
"server": false,
"browser": true,
"configPath": ["xpack", "observability_shared"],
"requiredPlugins": ["cases", "guidedOnboarding", "uiActions", "embeddable", "share"],
"optionalPlugins": [],
"requiredPlugins": ["cases", "uiActions", "embeddable", "share"],
"optionalPlugins": ["guidedOnboarding"],
"requiredBundles": ["data", "inspector", "kibanaReact", "kibanaUtils", "advancedSettings"],
"extraPublicDirs": ["common"]
}

View file

@ -26,7 +26,7 @@ export interface ObservabilitySharedSetup {
export interface ObservabilitySharedStart {
spaces?: SpacesPluginStart;
cases: CasesUiStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
setIsSidebarEnabled: (isEnabled: boolean) => void;
embeddable: EmbeddableStart;
share: SharePluginStart;
@ -73,7 +73,7 @@ export class ObservabilitySharedPlugin implements Plugin {
getUrlForApp: application.getUrlForApp,
navigateToApp: application.navigateToApp,
navigationSections$: this.navigationRegistry.sections$,
guidedOnboardingApi: plugins.guidedOnboarding.guidedOnboardingApi,
guidedOnboardingApi: plugins.guidedOnboarding?.guidedOnboardingApi,
getPageTemplateServices: () => ({ coreStart: core }),
isSidebarEnabled$: this.isSidebarEnabled$,
});

View file

@ -28,7 +28,6 @@
"eventLog",
"features",
"fieldFormats",
"guidedOnboarding",
"inspector",
"kubernetesSecurity",
"lens",
@ -69,7 +68,8 @@
"telemetry",
"dataViewFieldEditor",
"osquery",
"savedObjectsTaggingOss"
"savedObjectsTaggingOss",
"guidedOnboarding"
],
"requiredBundles": [
"esUiShared",

View file

@ -36,22 +36,26 @@ const initialState: TourContextValue = {
const TourContext = createContext<TourContextValue>(initialState);
export const RealTourContextProvider = ({ children }: { children: ReactChild }) => {
const { guidedOnboardingApi } = useKibana().services.guidedOnboarding;
const { guidedOnboarding } = useKibana().services;
const isRulesTourActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, SecurityStepId.rules).pipe(
// if no result after 30s the observable will error, but the error handler will just emit false
timeout(30000),
catchError((error) => of(false))
) ?? of(false),
guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$(siemGuideId, SecurityStepId.rules)
.pipe(
// if no result after 30s the observable will error, but the error handler will just emit false
timeout(30000),
catchError((error) => of(false))
) ?? of(false),
false
);
const isAlertsCasesTourActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, SecurityStepId.alertsCases).pipe(
// if no result after 30s the observable will error, but the error handler will just emit false
timeout(30000),
catchError((error) => of(false))
) ?? of(false),
guidedOnboarding?.guidedOnboardingApi
?.isGuideStepActive$(siemGuideId, SecurityStepId.alertsCases)
.pipe(
// if no result after 30s the observable will error, but the error handler will just emit false
timeout(30000),
catchError((error) => of(false))
) ?? of(false),
false
);
@ -79,12 +83,12 @@ export const RealTourContextProvider = ({ children }: { children: ReactChild })
const [completeStep, setCompleteStep] = useState<null | SecurityStepId>(null);
useEffect(() => {
if (!completeStep || !guidedOnboardingApi) {
if (!completeStep || !guidedOnboarding?.guidedOnboardingApi) {
return;
}
let ignore = false;
const complete = async () => {
await guidedOnboardingApi.completeGuideStep(siemGuideId, completeStep);
await guidedOnboarding?.guidedOnboardingApi?.completeGuideStep(siemGuideId, completeStep);
if (!ignore) {
setCompleteStep(null);
_setActiveStep(1);
@ -94,7 +98,7 @@ export const RealTourContextProvider = ({ children }: { children: ReactChild })
return () => {
ignore = true;
};
}, [completeStep, guidedOnboardingApi]);
}, [completeStep, guidedOnboarding]);
const endTourStep = useCallback((tourId: SecurityStepId) => {
setCompleteStep(tourId);

View file

@ -25,13 +25,10 @@ export const RulesPageTourComponent: React.FC<Props> = ({ children }) => {
tourPopoverWidth: 300,
};
const {
storage,
guidedOnboarding: { guidedOnboardingApi },
} = useKibana().services;
const { storage, guidedOnboarding } = useKibana().services;
const isGuidedOnboardingActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, 'rules') ?? of(false),
guidedOnboarding?.guidedOnboardingApi?.isGuideStepActive$(siemGuideId, 'rules') ?? of(false),
true
);

View file

@ -44,12 +44,12 @@ export enum GuidedOnboardingRulesStatus {
}
export const RulesManagementTour = () => {
const { guidedOnboardingApi } = useKibana().services.guidedOnboarding;
const { guidedOnboarding } = useKibana().services;
const { executeBulkAction } = useExecuteBulkAction();
const { actions } = useRulesTableContext();
const isRulesStepActive = useObservable(
guidedOnboardingApi?.isGuideStepActive$(siemGuideId, 'rules') ?? of(false),
guidedOnboarding?.guidedOnboardingApi?.isGuideStepActive$(siemGuideId, 'rules') ?? of(false),
false
);
@ -106,9 +106,9 @@ export const RulesManagementTour = () => {
// Synchronize the current "internal" tour step with the global one
useEffect(() => {
if (isRulesStepActive && tourStatus === GuidedOnboardingRulesStatus.completed) {
guidedOnboardingApi?.completeGuideStep('siem', 'rules');
guidedOnboarding?.guidedOnboardingApi?.completeGuideStep('siem', 'rules');
}
}, [guidedOnboardingApi, isRulesStepActive, tourStatus]);
}, [guidedOnboarding, isRulesStepActive, tourStatus]);
const enableDemoRule = useCallback(async () => {
if (demoRule) {

View file

@ -111,7 +111,7 @@ export interface StartPlugins {
embeddable: EmbeddableStart;
inspector: InspectorStart;
fleet?: FleetStart;
guidedOnboarding: GuidedOnboardingPluginStart;
guidedOnboarding?: GuidedOnboardingPluginStart;
kubernetesSecurity: KubernetesSecurityStart;
lens: LensPublicStart;
lists?: ListsPluginStart;

View file

@ -416,19 +416,19 @@ export class Plugin implements ISecuritySolutionPlugin {
depsStart.cloudExperiments
.getVariation('security-solutions.guided-onboarding-content', defaultGuideTranslations)
.then((variation) => {
plugins.guidedOnboarding.registerGuideConfig(
plugins.guidedOnboarding?.registerGuideConfig(
siemGuideId,
getSiemGuideConfig(variation)
);
});
} catch {
plugins.guidedOnboarding.registerGuideConfig(
plugins.guidedOnboarding?.registerGuideConfig(
siemGuideId,
getSiemGuideConfig(defaultGuideTranslations)
);
}
} else {
plugins.guidedOnboarding.registerGuideConfig(
plugins.guidedOnboarding?.registerGuideConfig(
siemGuideId,
getSiemGuideConfig(defaultGuideTranslations)
);

View file

@ -61,7 +61,7 @@ export interface SecuritySolutionPluginSetupDependencies {
usageCollection?: UsageCollectionPluginSetup;
licensing: LicensingPluginSetup;
osquery: OsqueryPluginSetup;
guidedOnboarding: GuidedOnboardingPluginSetup;
guidedOnboarding?: GuidedOnboardingPluginSetup;
unifiedSearch: UnifiedSearchServerPluginSetup;
}

View file

@ -30,7 +30,7 @@ import { login } from '../../../tasks/login';
import { visit } from '../../../tasks/navigation';
import { startAlertsCasesTour } from '../../../tasks/api_calls/tour';
describe('Guided onboarding tour', { tags: ['@ess', '@brokenInServerless'] }, () => {
describe('Guided onboarding tour', { tags: ['@ess'] }, () => {
before(() => {
cleanKibana();
login();