mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Guided onboarding] Update observability tour (#143006)
This commit is contained in:
parent
b232f35fd5
commit
048b11d274
14 changed files with 84 additions and 67 deletions
|
@ -48,7 +48,7 @@ export const Main = (props: MainProps) => {
|
|||
const [guidesState, setGuidesState] = useState<GuideState[] | undefined>(undefined);
|
||||
const [activeGuide, setActiveGuide] = useState<GuideState | undefined>(undefined);
|
||||
|
||||
const [selectedGuide, setSelectedGuide] = useState<GuideId | undefined>(undefined);
|
||||
const [selectedGuide, setSelectedGuide] = useState<GuideId | undefined>('observability');
|
||||
const [selectedStep, setSelectedStep] = useState<GuideStepIds | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -79,9 +79,13 @@ export const observabilityConfig: GuideConfig = {
|
|||
descriptionList: [
|
||||
i18n.translate('guidedOnboarding.observabilityGuide.tourObservabilityStep.description', {
|
||||
defaultMessage:
|
||||
'Take a look at the capabilities of our Observability solution and be inspired to add more integrations.',
|
||||
'Get familiar with the rest of Elastic Observability and explore even more integrations.',
|
||||
}),
|
||||
],
|
||||
location: {
|
||||
appID: 'observability',
|
||||
path: '/overview',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -160,7 +160,7 @@ export class ApiService implements GuidedOnboardingApi {
|
|||
/**
|
||||
* Marks a guide as inactive
|
||||
* This is useful for the dropdown panel, when a user quits a guide
|
||||
* @param {GuideState} guide (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide)
|
||||
* @param {GuideState} guide the selected guide state
|
||||
* @return {Promise} a promise with the updated guide state
|
||||
*/
|
||||
public async deactivateGuide(guide: GuideState): Promise<{ state: GuideState } | undefined> {
|
||||
|
|
|
@ -49,4 +49,4 @@
|
|||
"observability",
|
||||
"esUiShared"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
"triggersActionsUi",
|
||||
"inspector",
|
||||
"unifiedSearch",
|
||||
"security"
|
||||
"security",
|
||||
"guidedOnboarding"
|
||||
],
|
||||
"ui": true,
|
||||
"server": true,
|
||||
|
|
|
@ -11,6 +11,7 @@ import { shallow } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { of } from 'rxjs';
|
||||
import { getKibanaPageTemplateKibanaDependenciesMock as getPageTemplateServices } from '@kbn/shared-ux-page-kibana-template-mocks';
|
||||
import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks';
|
||||
|
||||
import { createNavigationRegistry } from '../../../services/navigation_registry';
|
||||
import { createLazyObservabilityPageTemplate } from './lazy_page_template';
|
||||
|
@ -54,6 +55,7 @@ describe('Page template', () => {
|
|||
navigateToApp: async () => {},
|
||||
navigationSections$: navigationRegistry.sections$,
|
||||
getPageTemplateServices,
|
||||
guidedOnboardingApi: guidedOnboardingMock.createStart().guidedOnboardingApi,
|
||||
});
|
||||
|
||||
const component = shallow(
|
||||
|
@ -82,6 +84,7 @@ describe('Page template', () => {
|
|||
rightSideItems: [<span>Test side item</span>],
|
||||
}}
|
||||
getPageTemplateServices={getPageTemplateServices}
|
||||
guidedOnboardingApi={guidedOnboardingMock.createStart().guidedOnboardingApi}
|
||||
>
|
||||
<div>Test structure</div>
|
||||
</ObservabilityPageTemplate>
|
||||
|
@ -103,6 +106,7 @@ describe('Page template', () => {
|
|||
rightSideItems: [<span>Test side item</span>],
|
||||
}}
|
||||
getPageTemplateServices={getPageTemplateServices}
|
||||
guidedOnboardingApi={guidedOnboardingMock.createStart().guidedOnboardingApi}
|
||||
>
|
||||
<div>Test structure</div>
|
||||
</ObservabilityPageTemplate>
|
||||
|
|
|
@ -22,6 +22,8 @@ import type {
|
|||
KibanaPageTemplateProps,
|
||||
KibanaPageTemplateKibanaDependencies,
|
||||
} from '@kbn/shared-ux-page-kibana-template';
|
||||
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
|
||||
import { ObservabilityAppServices } from '../../../application/types';
|
||||
import type { NavigationSection } from '../../../services/navigation_registry';
|
||||
import { ObservabilityTour } from '../tour';
|
||||
import { NavNameWithBadge, hideBadge } from './nav_name_with_badge';
|
||||
|
@ -49,6 +51,7 @@ export interface ObservabilityPageTemplateDependencies {
|
|||
navigateToApp: ApplicationStart['navigateToApp'];
|
||||
navigationSections$: Observable<NavigationSection[]>;
|
||||
getPageTemplateServices: () => KibanaPageTemplateKibanaDependencies;
|
||||
guidedOnboardingApi: GuidedOnboardingPluginStart['guidedOnboardingApi'];
|
||||
}
|
||||
|
||||
export type ObservabilityPageTemplateProps = ObservabilityPageTemplateDependencies &
|
||||
|
@ -66,13 +69,14 @@ export function ObservabilityPageTemplate({
|
|||
bottomBar,
|
||||
bottomBarProps,
|
||||
pageSectionProps,
|
||||
guidedOnboardingApi,
|
||||
...pageTemplateProps
|
||||
}: ObservabilityPageTemplateProps): React.ReactElement | null {
|
||||
const sections = useObservable(navigationSections$, []);
|
||||
const currentAppId = useObservable(currentAppId$, undefined);
|
||||
const { pathname: currentPath } = useLocation();
|
||||
|
||||
const { services } = useKibana();
|
||||
const { services } = useKibana<ObservabilityAppServices>();
|
||||
|
||||
const sideNavItems = useMemo<Array<EuiSideNavItemType<unknown>>>(
|
||||
() =>
|
||||
|
@ -141,6 +145,7 @@ export function ObservabilityPageTemplate({
|
|||
<ObservabilityTour
|
||||
navigateToApp={navigateToApp}
|
||||
prependBasePath={services?.http?.basePath.prepend}
|
||||
guidedOnboardingApi={guidedOnboardingApi}
|
||||
isPageDataLoaded={isPageDataLoaded}
|
||||
// The tour is dependent on the solution nav, and should not render if it is not visible
|
||||
showTour={showSolutionNav}
|
||||
|
|
|
@ -5,9 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export {
|
||||
ObservabilityTour,
|
||||
observTourActiveStorageKey,
|
||||
observTourStepStorageKey,
|
||||
useObservabilityTourContext,
|
||||
} from './tour';
|
||||
export { ObservabilityTour, observTourStepStorageKey, useObservabilityTourContext } from './tour';
|
||||
|
|
|
@ -108,11 +108,11 @@ export const tourStepsConfig: TourStep[] = [
|
|||
},
|
||||
{
|
||||
title: i18n.translate('xpack.observability.tour.guidedSetupStep.tourTitle', {
|
||||
defaultMessage: 'Now add your data!',
|
||||
defaultMessage: 'Do more with Elastic Observability',
|
||||
}),
|
||||
content: i18n.translate('xpack.observability.tour.guidedSetupStep.tourContent', {
|
||||
defaultMessage:
|
||||
'The easiest way to get going with Elastic Observability is to follow the steps in the data assistant.',
|
||||
'The easiest way to continue with Elastic Observability is to follow recommended next steps in the data assistant.',
|
||||
}),
|
||||
anchor: '#guidedSetupButton',
|
||||
anchorPosition: 'rightUp',
|
||||
|
|
|
@ -29,6 +29,9 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { ApplicationStart } from '@kbn/core/public';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { of } from 'rxjs';
|
||||
import type { GuidedOnboardingApi } from '@kbn/guided-onboarding-plugin/public/types';
|
||||
import { observabilityAppId } from '../../../../common';
|
||||
import { tourStepsConfig } from './steps_config';
|
||||
|
||||
|
@ -38,9 +41,8 @@ const offset: EuiTourStepProps['offset'] = 30;
|
|||
const repositionOnScroll: EuiTourStepProps['repositionOnScroll'] = true;
|
||||
|
||||
const overviewPath = '/overview';
|
||||
const guidedSetupStep = 6;
|
||||
const dataAssistantStep = 6;
|
||||
|
||||
export const observTourActiveStorageKey = 'guidedOnboarding.observability.tourActive';
|
||||
export const observTourStepStorageKey = 'guidedOnboarding.observability.tourStep';
|
||||
|
||||
const getSteps = ({
|
||||
|
@ -140,35 +142,6 @@ const getSteps = ({
|
|||
});
|
||||
};
|
||||
|
||||
interface TourState {
|
||||
activeStep: number;
|
||||
isTourActive: boolean;
|
||||
}
|
||||
|
||||
const getInitialTourState = ({
|
||||
prevIsTourActive,
|
||||
prevActiveStep,
|
||||
}: {
|
||||
prevIsTourActive: string | null;
|
||||
prevActiveStep: string | null;
|
||||
}): TourState => {
|
||||
if (prevIsTourActive === null) {
|
||||
return {
|
||||
activeStep: 1,
|
||||
// Tour is inactive by default
|
||||
isTourActive: false,
|
||||
};
|
||||
}
|
||||
|
||||
const isTourActive = prevIsTourActive === 'true';
|
||||
const activeStep = prevActiveStep === null ? 1 : Number(prevActiveStep);
|
||||
|
||||
return {
|
||||
activeStep,
|
||||
isTourActive,
|
||||
};
|
||||
};
|
||||
|
||||
export interface ObservabilityTourContextValue {
|
||||
endTour: () => void;
|
||||
isTourVisible: boolean;
|
||||
|
@ -185,22 +158,26 @@ export function ObservabilityTour({
|
|||
isPageDataLoaded,
|
||||
showTour,
|
||||
prependBasePath,
|
||||
guidedOnboardingApi,
|
||||
}: {
|
||||
children: ({ isTourVisible }: { isTourVisible: boolean }) => ReactNode;
|
||||
navigateToApp: ApplicationStart['navigateToApp'];
|
||||
isPageDataLoaded: boolean;
|
||||
showTour: boolean;
|
||||
prependBasePath?: (imageName: string) => string;
|
||||
guidedOnboardingApi?: GuidedOnboardingApi;
|
||||
}) {
|
||||
const prevIsTourActive = localStorage.getItem(observTourActiveStorageKey);
|
||||
const prevActiveStep = localStorage.getItem(observTourStepStorageKey);
|
||||
const initialActiveStep = prevActiveStep === null ? 1 : Number(prevActiveStep);
|
||||
|
||||
const { activeStep: initialActiveStep, isTourActive: initialIsTourActive } = getInitialTourState({
|
||||
prevIsTourActive,
|
||||
prevActiveStep,
|
||||
});
|
||||
const isGuidedOnboardingActive = useObservable(
|
||||
// if guided onboarding is not available, return false
|
||||
guidedOnboardingApi
|
||||
? guidedOnboardingApi.isGuideStepActive$('observability', 'tour_observability')
|
||||
: of(false)
|
||||
);
|
||||
|
||||
const [isTourActive, setIsTourActive] = useState(initialIsTourActive);
|
||||
const [isTourActive, setIsTourActive] = useState(false);
|
||||
const [activeStep, setActiveStep] = useState(initialActiveStep);
|
||||
|
||||
const { pathname: currentPath } = useLocation();
|
||||
|
@ -213,16 +190,19 @@ export function ObservabilityTour({
|
|||
setActiveStep((prevState) => prevState + 1);
|
||||
}, []);
|
||||
|
||||
const endTour = useCallback(() => {
|
||||
// Reset tour state
|
||||
setIsTourActive(false);
|
||||
const endTour = useCallback(async () => {
|
||||
// Mark the onboarding guide step as complete
|
||||
if (guidedOnboardingApi) {
|
||||
await guidedOnboardingApi.completeGuideStep('observability', 'tour_observability');
|
||||
}
|
||||
// Reset EuiTour step state
|
||||
setActiveStep(1);
|
||||
}, []);
|
||||
}, [guidedOnboardingApi]);
|
||||
|
||||
/**
|
||||
* The tour should only be visible if the following conditions are met:
|
||||
* - Only pages with the side nav should show the tour (showTour === true)
|
||||
* - Tour is set to active per localStorage setting (isTourActive === true)
|
||||
* - Tour is set to active per the guided onboarding service (isTourActive === true)
|
||||
* - Any page data must be loaded in order for the tour to render correctly
|
||||
* - The tour should only render on medium-large screens
|
||||
*/
|
||||
|
@ -230,17 +210,17 @@ export function ObservabilityTour({
|
|||
|
||||
const context: ObservabilityTourContextValue = { endTour, isTourVisible };
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(observTourActiveStorageKey, String(isTourActive));
|
||||
}, [isTourActive]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(observTourStepStorageKey, String(activeStep));
|
||||
}, [activeStep]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsTourActive(Boolean(isGuidedOnboardingActive));
|
||||
}, [isGuidedOnboardingActive]);
|
||||
|
||||
useEffect(() => {
|
||||
// The user must be on the overview page to view the data assistant step in the tour
|
||||
if (isTourActive && isOverviewPage === false && activeStep === guidedSetupStep) {
|
||||
if (isTourActive && isOverviewPage === false && activeStep === dataAssistantStep) {
|
||||
navigateToApp(observabilityAppId, {
|
||||
path: overviewPath,
|
||||
});
|
||||
|
|
|
@ -37,6 +37,7 @@ import {
|
|||
RuleTypeRegistryContract,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { SecurityPluginStart } from '@kbn/security-plugin/public';
|
||||
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
|
||||
import { observabilityAppId, observabilityFeatureId, casesPath } from '../common';
|
||||
import { createLazyObservabilityPageTemplate } from './components/shared';
|
||||
import { registerDataHandler } from './data_handler';
|
||||
|
@ -92,6 +93,7 @@ export interface ObservabilityPublicPluginsStart {
|
|||
ruleTypeRegistry: RuleTypeRegistryContract;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
security: SecurityPluginStart;
|
||||
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||
}
|
||||
|
||||
export type ObservabilityPublicStart = ReturnType<Plugin['start']>;
|
||||
|
@ -300,6 +302,7 @@ export class Plugin
|
|||
getUrlForApp: application.getUrlForApp,
|
||||
navigateToApp: application.navigateToApp,
|
||||
navigationSections$: this.navigationRegistry.sections$,
|
||||
guidedOnboardingApi: pluginsStart.guidedOnboarding.guidedOnboardingApi,
|
||||
getPageTemplateServices: () => ({ coreStart }),
|
||||
});
|
||||
|
||||
|
|
|
@ -30,5 +30,6 @@
|
|||
{ "path": "../timelines/tsconfig.json"},
|
||||
{ "path": "../translations/tsconfig.json" },
|
||||
{ "path": "../../../src/plugins/unified_search/tsconfig.json"},
|
||||
{ "path": "../../../src/plugins/guided_onboarding/tsconfig.json"},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import {
|
||||
observTourActiveStorageKey,
|
||||
observTourStepStorageKey,
|
||||
} from '@kbn/observability-plugin/public/components/shared/tour';
|
||||
import { observTourStepStorageKey } from '@kbn/observability-plugin/public/components/shared/tour';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
|
@ -17,9 +14,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const esArchiver = getService('esArchiver');
|
||||
const pageObjects = getPageObjects(['common', 'infraHome']);
|
||||
const find = getService('find');
|
||||
const supertest = getService('supertest');
|
||||
|
||||
const setInitialTourState = async (activeStep?: number) => {
|
||||
await browser.setLocalStorageItem(observTourActiveStorageKey, 'true');
|
||||
await browser.setLocalStorageItem(observTourStepStorageKey, String(activeStep || 1));
|
||||
await browser.refresh();
|
||||
};
|
||||
|
@ -36,11 +33,37 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
|
||||
after(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs');
|
||||
await browser.removeLocalStorageItem(observTourActiveStorageKey);
|
||||
await browser.removeLocalStorageItem(observTourStepStorageKey);
|
||||
});
|
||||
|
||||
describe('Tour enabled', () => {
|
||||
beforeEach(async () => {
|
||||
// Activate the Observability guide, step 3, in order to trigger the EuiTour
|
||||
await supertest
|
||||
.put(`/api/guided_onboarding/state`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
status: 'in_progress',
|
||||
guideId: 'observability',
|
||||
isActive: true,
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'view_dashboard',
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'tour_observability',
|
||||
status: 'in_progress',
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('can complete tour', async () => {
|
||||
await setInitialTourState();
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ export default async function ({ readConfigFile }) {
|
|||
'--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true',
|
||||
'--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects,
|
||||
'--uiSettings.overrides.observability:enableNewSyntheticsView=true', // for OSS test management/_import_objects,
|
||||
'--guidedOnboarding.ui=true', // Enable guided onboarding for infra/tour.ts tests
|
||||
],
|
||||
},
|
||||
uiSettings: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue