mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Guided onboarding] Add a config for test guide (#143973)
* [Guided onboarding] Add a guide config for testing and update the example plugin to use it instead of search * [Guided onboarding] Update the API tests with the test guide config * [Guided onboarding] Address CR comments * [Guided onboarding] Delete unneeded code in examples * Update src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts Co-authored-by: Alison Goryachev <alisonmllr20@gmail.com> Co-authored-by: Alison Goryachev <alisonmllr20@gmail.com>
This commit is contained in:
parent
bbbf9f8985
commit
460cf89d5f
13 changed files with 233 additions and 287 deletions
|
@ -59,7 +59,7 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps
|
|||
<StepOne guidedOnboarding={guidedOnboarding} />
|
||||
</Route>
|
||||
<Route exact path="/stepTwo">
|
||||
<StepTwo guidedOnboarding={guidedOnboarding} />
|
||||
<StepTwo />
|
||||
</Route>
|
||||
<Route exact path="/stepThree">
|
||||
<StepThree guidedOnboarding={guidedOnboarding} />
|
||||
|
|
|
@ -259,6 +259,7 @@ export const Main = (props: MainProps) => {
|
|||
{ value: 'observability', text: 'observability' },
|
||||
{ value: 'security', text: 'security' },
|
||||
{ value: 'search', text: 'search' },
|
||||
{ value: 'testGuide', text: 'test guide' },
|
||||
]}
|
||||
value={selectedGuide}
|
||||
onChange={(e) => {
|
||||
|
@ -294,7 +295,7 @@ export const Main = (props: MainProps) => {
|
|||
<h3>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingExample.main.examplePages.title"
|
||||
defaultMessage="Example pages"
|
||||
defaultMessage="Example pages for test guide"
|
||||
/>
|
||||
</h3>
|
||||
</EuiText>
|
||||
|
@ -316,6 +317,14 @@ export const Main = (props: MainProps) => {
|
|||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={() => history.push('stepThree')}>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingExample.main.examplePages.stepThree.link"
|
||||
defaultMessage="Step 3"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageContentBody>
|
||||
</>
|
||||
|
|
|
@ -32,7 +32,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
|
|||
const [isTourStepOpen, setIsTourStepOpen] = useState<boolean>(false);
|
||||
|
||||
const isTourActive = useObservable(
|
||||
guidedOnboardingApi!.isGuideStepActive$('search', 'add_data'),
|
||||
guidedOnboardingApi!.isGuideStepActive$('testGuide', 'step1'),
|
||||
false
|
||||
);
|
||||
useEffect(() => {
|
||||
|
@ -45,7 +45,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
|
|||
<h2>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingExample.stepOne.title"
|
||||
defaultMessage="Example step Add data"
|
||||
defaultMessage="Example step 1"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
@ -56,7 +56,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
|
|||
<FormattedMessage
|
||||
id="guidedOnboardingExample.guidesSelection.stepOne.explanation"
|
||||
defaultMessage="The code on this page is listening to the guided setup state with a useObservable hook. If the state is set to
|
||||
Search guide, step Add data, a EUI tour will be displayed, pointing to the button below."
|
||||
Test guide, step 1, a EUI tour will be displayed, pointing to the button below."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
@ -72,12 +72,12 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>
|
|||
onFinish={() => setIsTourStepOpen(false)}
|
||||
step={1}
|
||||
stepsTotal={1}
|
||||
title="Step Add data"
|
||||
title="Step 1"
|
||||
anchorPosition="rightUp"
|
||||
>
|
||||
<EuiButton
|
||||
onClick={async () => {
|
||||
await guidedOnboardingApi?.completeGuideStep('search', 'add_data');
|
||||
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step1');
|
||||
}}
|
||||
>
|
||||
Complete step 1
|
||||
|
|
|
@ -30,7 +30,7 @@ export const StepThree = (props: StepThreeProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
const subscription = guidedOnboardingApi
|
||||
?.isGuideStepActive$('search', 'search_experience')
|
||||
?.isGuideStepActive$('testGuide', 'step3')
|
||||
.subscribe((isStepActive) => {
|
||||
setIsTourStepOpen(isStepActive);
|
||||
});
|
||||
|
@ -53,9 +53,17 @@ export const StepThree = (props: StepThreeProps) => {
|
|||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingExample.guidesSelection.stepThree.explanation"
|
||||
defaultMessage="The code on this page is listening to the guided setup state using an Observable subscription. If the state is set to
|
||||
Search guide, step Search experience, a EUI tour will be displayed, pointing to the button below."
|
||||
id="guidedOnboardingExample.guidesSelection.stepThree.explanation1"
|
||||
defaultMessage="The code on this page is listening to the guided setup state using an Observable subscription.
|
||||
If the state is set to Test, step 3, a EUI tour will be displayed, pointing to the button below."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingExample.guidesSelection.stepTwo.explanation2"
|
||||
defaultMessage="This page is used for the manual completion of Test guide, step 3. After clicking the page,
|
||||
the manual completion popover
|
||||
should appear on the header button 'Setup guide' to open the panel and mark the step done."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
@ -73,12 +81,12 @@ export const StepThree = (props: StepThreeProps) => {
|
|||
}}
|
||||
step={1}
|
||||
stepsTotal={1}
|
||||
title="Step Build search experience"
|
||||
title="Step 3"
|
||||
anchorPosition="rightUp"
|
||||
>
|
||||
<EuiButton
|
||||
onClick={async () => {
|
||||
await guidedOnboardingApi?.completeGuideStep('search', 'search_experience');
|
||||
await guidedOnboardingApi?.completeGuideStep('testGuide', 'step3');
|
||||
}}
|
||||
>
|
||||
Complete step 3
|
||||
|
|
|
@ -6,37 +6,17 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { EuiButton, EuiSpacer, EuiText, EuiTitle, EuiTourStep } from '@elastic/eui';
|
||||
import { 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,
|
||||
} from '@elastic/eui';
|
||||
|
||||
interface StepTwoProps {
|
||||
guidedOnboarding: GuidedOnboardingPluginStart;
|
||||
}
|
||||
|
||||
export const StepTwo = (props: StepTwoProps) => {
|
||||
const {
|
||||
guidedOnboarding: { guidedOnboardingApi },
|
||||
} = props;
|
||||
|
||||
const [isTourStepOpen, setIsTourStepOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = guidedOnboardingApi
|
||||
?.isGuideStepActive$('search', 'browse_docs')
|
||||
.subscribe((isStepActive) => {
|
||||
setIsTourStepOpen(isStepActive);
|
||||
});
|
||||
return () => subscription?.unsubscribe();
|
||||
}, [guidedOnboardingApi]);
|
||||
|
||||
export const StepTwo = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiPageContentHeader>
|
||||
|
@ -54,36 +34,11 @@ export const StepTwo = (props: StepTwoProps) => {
|
|||
<p>
|
||||
<FormattedMessage
|
||||
id="guidedOnboardingExample.guidesSelection.stepTwo.explanation"
|
||||
defaultMessage="The code on this page is listening to the guided setup state using an Observable subscription. If the state is set to
|
||||
Search guide, step Browse documents, a EUI tour will be displayed, pointing to the button below."
|
||||
defaultMessage="This page is used for the manual completion of Test guide, step 2. The manual completion popover
|
||||
should appear on the header button 'Setup guide' to open the panel and mark the step done."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<EuiTourStep
|
||||
content={
|
||||
<EuiText>
|
||||
<p>Click this button to complete step 2.</p>
|
||||
</EuiText>
|
||||
}
|
||||
isStepOpen={isTourStepOpen}
|
||||
minWidth={300}
|
||||
onFinish={() => {
|
||||
setIsTourStepOpen(false);
|
||||
}}
|
||||
step={1}
|
||||
stepsTotal={1}
|
||||
title="Step Browse documents"
|
||||
anchorPosition="rightUp"
|
||||
>
|
||||
<EuiButton
|
||||
onClick={async () => {
|
||||
await guidedOnboardingApi?.completeGuideStep('search', 'browse_docs');
|
||||
}}
|
||||
>
|
||||
Complete step 2
|
||||
</EuiButton>
|
||||
</EuiTourStep>
|
||||
</EuiPageContentBody>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { EuiCard, EuiText, EuiImage } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { GuideId } from '../../types';
|
||||
|
||||
type UseCaseConstants = {
|
||||
[key in UseCase]: {
|
||||
|
@ -53,7 +52,7 @@ const constants: UseCaseConstants = {
|
|||
export type UseCase = 'search' | 'observability' | 'security';
|
||||
|
||||
export interface UseCaseCardProps {
|
||||
useCase: GuideId;
|
||||
useCase: UseCase;
|
||||
title: string;
|
||||
description: string;
|
||||
footer: ReactNode;
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type GuideId = 'observability' | 'security' | 'search';
|
||||
export type GuideId = 'observability' | 'security' | 'search' | 'testGuide';
|
||||
|
||||
export type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability';
|
||||
export type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases';
|
||||
export type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience';
|
||||
type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability';
|
||||
type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases';
|
||||
type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience';
|
||||
type TestGuideIds = 'step1' | 'step2' | 'step3';
|
||||
|
||||
export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds;
|
||||
export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds | TestGuideIds;
|
||||
|
||||
export interface GuideState {
|
||||
guideId: GuideId;
|
||||
|
|
|
@ -10,9 +10,11 @@ import type { GuidesConfig } from '../../types';
|
|||
import { securityConfig } from './security';
|
||||
import { observabilityConfig } from './observability';
|
||||
import { searchConfig } from './search';
|
||||
import { testGuideConfig } from './test_guide';
|
||||
|
||||
export const guidesConfig: GuidesConfig = {
|
||||
security: securityConfig,
|
||||
observability: observabilityConfig,
|
||||
search: searchConfig,
|
||||
testGuide: testGuideConfig,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 testGuideConfig: GuideConfig = {
|
||||
title: 'Test guide for development',
|
||||
description: `This guide is used to test the guided onboarding UI while in development and to run automated tests for the API and UI components.`,
|
||||
guideName: 'Testing example',
|
||||
docs: {
|
||||
text: 'Testing example docs',
|
||||
url: 'example.com',
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
id: 'step1',
|
||||
title: 'Step 1 (completed via an API request)',
|
||||
descriptionList: [
|
||||
`This step is directly completed by clicking the button that uses the API function 'completeGuideStep`,
|
||||
'Navigate to /guidedOnboardingExample/stepOne to complete the step.',
|
||||
],
|
||||
location: {
|
||||
appID: 'guidedOnboardingExample',
|
||||
path: 'stepOne',
|
||||
},
|
||||
integration: 'testIntegration',
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
title: 'Step 2 (manual completion after navigation)',
|
||||
descriptionList: [
|
||||
'This step is set to ready_to_complete on page navigation.',
|
||||
'After that click the popover on the guide button in the header and mark the step done',
|
||||
],
|
||||
location: {
|
||||
appID: 'guidedOnboardingExample',
|
||||
path: 'stepTwo',
|
||||
},
|
||||
manualCompletion: {
|
||||
title: 'Manual completion step title',
|
||||
description:
|
||||
'Mark the step complete by opening the panel and clicking the button "Mark done"',
|
||||
readyToCompleteOnNavigation: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'step3',
|
||||
title: 'Step 3 (manual completion after click)',
|
||||
descriptionList: [
|
||||
'This step is completed by clicking a button on the page and then clicking the popover on the guide button in the header and marking the step done',
|
||||
],
|
||||
manualCompletion: {
|
||||
title: 'Manual completion step title',
|
||||
description:
|
||||
'Mark the step complete by opening the panel and clicking the button "Mark done"',
|
||||
},
|
||||
location: {
|
||||
appID: 'guidedOnboardingExample',
|
||||
path: 'stepThree',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -6,84 +6,78 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { GuideState } from '@kbn/guided-onboarding';
|
||||
import type { GuideState, GuideId, GuideStepIds } from '@kbn/guided-onboarding';
|
||||
|
||||
export const searchAddDataActiveState: GuideState = {
|
||||
guideId: 'search',
|
||||
export const testGuide: GuideId = 'testGuide';
|
||||
export const testGuideFirstStep: GuideStepIds = 'step1';
|
||||
export const testGuideManualCompletionStep = 'step2';
|
||||
export const testGuideLastStep: GuideStepIds = 'step3';
|
||||
export const testIntegration = 'testIntegration';
|
||||
export const wrongIntegration = 'notTestIntegration';
|
||||
|
||||
export const testGuideStep1ActiveState: GuideState = {
|
||||
guideId: 'testGuide',
|
||||
isActive: true,
|
||||
status: 'in_progress',
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
id: 'step1',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
id: 'browse_docs',
|
||||
id: 'step2',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
id: 'search_experience',
|
||||
id: 'step3',
|
||||
status: 'inactive',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const securityAddDataInProgressState: GuideState = {
|
||||
guideId: 'security',
|
||||
status: 'in_progress',
|
||||
isActive: true,
|
||||
export const testGuideStep1InProgressState: GuideState = {
|
||||
...testGuideStep1ActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
status: 'in_progress',
|
||||
},
|
||||
{
|
||||
id: 'rules',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
id: 'alertsCases',
|
||||
status: 'inactive',
|
||||
id: testGuideStep1ActiveState.steps[0].id,
|
||||
status: 'in_progress', // update the first step status
|
||||
},
|
||||
testGuideStep1ActiveState.steps[1],
|
||||
testGuideStep1ActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
|
||||
export const securityRulesActiveState: GuideState = {
|
||||
guideId: 'security',
|
||||
isActive: true,
|
||||
status: 'in_progress',
|
||||
export const testGuideStep2ActiveState: GuideState = {
|
||||
...testGuideStep1ActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
...testGuideStep1ActiveState.steps[0],
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'rules',
|
||||
id: testGuideStep1ActiveState.steps[1].id,
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
id: 'alertsCases',
|
||||
status: 'inactive',
|
||||
},
|
||||
testGuideStep1ActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
|
||||
export const noGuideActiveState: GuideState = {
|
||||
guideId: 'security',
|
||||
status: 'in_progress',
|
||||
isActive: false,
|
||||
export const testGuideStep2InProgressState: GuideState = {
|
||||
...testGuideStep1ActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
...testGuideStep1ActiveState.steps[0],
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: testGuideStep1ActiveState.steps[1].id,
|
||||
status: 'in_progress',
|
||||
},
|
||||
{
|
||||
id: 'rules',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
id: 'alertsCases',
|
||||
status: 'inactive',
|
||||
},
|
||||
testGuideStep1ActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
|
||||
export const testGuideNotActiveState: GuideState = {
|
||||
...testGuideStep1ActiveState,
|
||||
isActive: false,
|
||||
};
|
||||
|
|
|
@ -12,20 +12,20 @@ import type { GuideState } from '@kbn/guided-onboarding';
|
|||
import { firstValueFrom, Subscription } from 'rxjs';
|
||||
|
||||
import { API_BASE_PATH } from '../../common/constants';
|
||||
import { guidesConfig } from '../constants/guides_config';
|
||||
import { ApiService } from './api';
|
||||
import {
|
||||
noGuideActiveState,
|
||||
searchAddDataActiveState,
|
||||
securityAddDataInProgressState,
|
||||
securityRulesActiveState,
|
||||
testGuide,
|
||||
testGuideFirstStep,
|
||||
testGuideManualCompletionStep,
|
||||
testGuideStep1ActiveState,
|
||||
testGuideStep1InProgressState,
|
||||
testGuideStep2ActiveState,
|
||||
testGuideNotActiveState,
|
||||
testIntegration,
|
||||
wrongIntegration,
|
||||
testGuideStep2InProgressState,
|
||||
} from './api.mocks';
|
||||
|
||||
const searchGuide = 'search';
|
||||
const firstStep = guidesConfig[searchGuide].steps[0].id;
|
||||
const endpointIntegration = 'endpoint';
|
||||
const kubernetesIntegration = 'kubernetes';
|
||||
|
||||
describe('GuidedOnboarding ApiService', () => {
|
||||
let httpClient: jest.Mocked<HttpSetup>;
|
||||
let apiService: ApiService;
|
||||
|
@ -34,7 +34,7 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
beforeEach(() => {
|
||||
httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' });
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [securityAddDataInProgressState],
|
||||
state: [testGuideStep1ActiveState],
|
||||
});
|
||||
apiService = new ApiService();
|
||||
apiService.setup(httpClient);
|
||||
|
@ -57,10 +57,10 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
});
|
||||
|
||||
it('broadcasts the updated state', async () => {
|
||||
await apiService.activateGuide(searchGuide, searchAddDataActiveState);
|
||||
await apiService.activateGuide(testGuide, testGuideStep1ActiveState);
|
||||
|
||||
const state = await firstValueFrom(apiService.fetchActiveGuideState$());
|
||||
expect(state).toEqual(searchAddDataActiveState);
|
||||
expect(state).toEqual(testGuideStep1ActiveState);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -74,12 +74,12 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
describe('deactivateGuide', () => {
|
||||
it('deactivates an existing guide', async () => {
|
||||
await apiService.deactivateGuide(searchAddDataActiveState);
|
||||
await apiService.deactivateGuide(testGuideStep1ActiveState);
|
||||
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(1);
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify({
|
||||
...searchAddDataActiveState,
|
||||
...testGuideStep1ActiveState,
|
||||
isActive: false,
|
||||
}),
|
||||
});
|
||||
|
@ -88,17 +88,7 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
describe('updateGuideState', () => {
|
||||
it('sends a request to the put API', async () => {
|
||||
const updatedState: GuideState = {
|
||||
...searchAddDataActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: searchAddDataActiveState.steps[0].id,
|
||||
status: 'in_progress', // update the first step status
|
||||
},
|
||||
searchAddDataActiveState.steps[1],
|
||||
searchAddDataActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
const updatedState: GuideState = testGuideStep1InProgressState;
|
||||
await apiService.updateGuideState(updatedState, false);
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(1);
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
|
@ -109,20 +99,11 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
describe('isGuideStepActive$', () => {
|
||||
it('returns true if the step has been started', (done) => {
|
||||
const updatedState: GuideState = {
|
||||
...searchAddDataActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: searchAddDataActiveState.steps[0].id,
|
||||
status: 'in_progress',
|
||||
},
|
||||
searchAddDataActiveState.steps[1],
|
||||
searchAddDataActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
const updatedState: GuideState = testGuideStep1InProgressState;
|
||||
apiService.updateGuideState(updatedState, false);
|
||||
|
||||
subscription = apiService
|
||||
.isGuideStepActive$(searchGuide, firstStep)
|
||||
.isGuideStepActive$(testGuide, testGuideFirstStep)
|
||||
.subscribe((isStepActive) => {
|
||||
if (isStepActive) {
|
||||
done();
|
||||
|
@ -131,9 +112,8 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
});
|
||||
|
||||
it('returns false if the step is not been started', (done) => {
|
||||
apiService.updateGuideState(searchAddDataActiveState, false);
|
||||
subscription = apiService
|
||||
.isGuideStepActive$(searchGuide, firstStep)
|
||||
.isGuideStepActive$(testGuide, testGuideFirstStep)
|
||||
.subscribe((isStepActive) => {
|
||||
if (!isStepActive) {
|
||||
done();
|
||||
|
@ -144,56 +124,44 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
describe('activateGuide', () => {
|
||||
it('activates a new guide', async () => {
|
||||
await apiService.activateGuide(searchGuide);
|
||||
// update the mock to no active guides
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
|
||||
await apiService.activateGuide(testGuide);
|
||||
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(1);
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify({
|
||||
isActive: true,
|
||||
status: 'not_started',
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
status: 'active',
|
||||
},
|
||||
{
|
||||
id: 'browse_docs',
|
||||
status: 'inactive',
|
||||
},
|
||||
{
|
||||
id: 'search_experience',
|
||||
status: 'inactive',
|
||||
},
|
||||
],
|
||||
guideId: searchGuide,
|
||||
}),
|
||||
body: JSON.stringify({ ...testGuideStep1ActiveState, status: 'not_started' }),
|
||||
});
|
||||
});
|
||||
|
||||
it('reactivates a guide that has already been started', async () => {
|
||||
await apiService.activateGuide(searchGuide, searchAddDataActiveState);
|
||||
await apiService.activateGuide(testGuide, testGuideStep1ActiveState);
|
||||
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(1);
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify(searchAddDataActiveState),
|
||||
body: JSON.stringify(testGuideStep1ActiveState),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('completeGuide', () => {
|
||||
const readyToCompleteGuideState: GuideState = {
|
||||
...searchAddDataActiveState,
|
||||
...testGuideStep1ActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
...testGuideStep1ActiveState.steps[0],
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'browse_docs',
|
||||
...testGuideStep1ActiveState.steps[1],
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'search_experience',
|
||||
...testGuideStep1ActiveState.steps[2],
|
||||
status: 'complete',
|
||||
},
|
||||
],
|
||||
|
@ -204,7 +172,7 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
});
|
||||
|
||||
it('updates the selected guide and marks it as complete', async () => {
|
||||
await apiService.completeGuide(searchGuide);
|
||||
await apiService.completeGuide(testGuide);
|
||||
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify({
|
||||
|
@ -222,51 +190,39 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
it('returns undefined if the selected guide has uncompleted steps', async () => {
|
||||
const incompleteGuideState: GuideState = {
|
||||
...searchAddDataActiveState,
|
||||
...testGuideStep1ActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: 'add_data',
|
||||
...testGuideStep1ActiveState.steps[0],
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'browse_docs',
|
||||
...testGuideStep1ActiveState.steps[1],
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: 'search_experience',
|
||||
...testGuideStep1ActiveState.steps[2],
|
||||
status: 'in_progress',
|
||||
},
|
||||
],
|
||||
};
|
||||
await apiService.updateGuideState(incompleteGuideState, false);
|
||||
|
||||
const completedState = await apiService.completeGuide(searchGuide);
|
||||
const completedState = await apiService.completeGuide(testGuide);
|
||||
expect(completedState).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('startGuideStep', () => {
|
||||
beforeEach(async () => {
|
||||
await apiService.updateGuideState(searchAddDataActiveState, false);
|
||||
await apiService.updateGuideState(testGuideStep1ActiveState, false);
|
||||
});
|
||||
|
||||
it('updates the selected step and marks it as in_progress', async () => {
|
||||
await apiService.startGuideStep(searchGuide, firstStep);
|
||||
await apiService.startGuideStep(testGuide, testGuideFirstStep);
|
||||
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify({
|
||||
...searchAddDataActiveState,
|
||||
isActive: true,
|
||||
status: 'in_progress',
|
||||
steps: [
|
||||
{
|
||||
id: searchAddDataActiveState.steps[0].id,
|
||||
status: 'in_progress',
|
||||
},
|
||||
searchAddDataActiveState.steps[1],
|
||||
searchAddDataActiveState.steps[2],
|
||||
],
|
||||
}),
|
||||
body: JSON.stringify(testGuideStep1InProgressState),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -278,76 +234,35 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
describe('completeGuideStep', () => {
|
||||
it(`completes the step when it's in progress`, async () => {
|
||||
const updatedState: GuideState = {
|
||||
...searchAddDataActiveState,
|
||||
steps: [
|
||||
{
|
||||
id: searchAddDataActiveState.steps[0].id,
|
||||
status: 'in_progress', // Mark a step as in_progress in order to test the "completeGuideStep" behavior
|
||||
},
|
||||
searchAddDataActiveState.steps[1],
|
||||
searchAddDataActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
await apiService.updateGuideState(updatedState, false);
|
||||
await apiService.updateGuideState(testGuideStep1InProgressState, false);
|
||||
|
||||
await apiService.completeGuideStep(searchGuide, firstStep);
|
||||
await apiService.completeGuideStep(testGuide, testGuideFirstStep);
|
||||
|
||||
// Once on update, once on complete
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(2);
|
||||
// Verify the completed step now has a "complete" status, and the subsequent step is "active"
|
||||
expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify({
|
||||
...updatedState,
|
||||
steps: [
|
||||
{
|
||||
id: searchAddDataActiveState.steps[0].id,
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
id: searchAddDataActiveState.steps[1].id,
|
||||
status: 'active',
|
||||
},
|
||||
searchAddDataActiveState.steps[2],
|
||||
],
|
||||
}),
|
||||
body: JSON.stringify({ ...testGuideStep2ActiveState }),
|
||||
});
|
||||
});
|
||||
|
||||
it(`marks the step as 'ready_to_complete' if it's configured for manual completion`, async () => {
|
||||
const securityRulesInProgressState = {
|
||||
...securityRulesActiveState,
|
||||
steps: [
|
||||
securityRulesActiveState.steps[0],
|
||||
{
|
||||
id: securityRulesActiveState.steps[1].id,
|
||||
status: 'in_progress',
|
||||
},
|
||||
securityRulesActiveState.steps[2],
|
||||
],
|
||||
};
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [securityRulesInProgressState],
|
||||
state: [testGuideStep2InProgressState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
|
||||
await apiService.completeGuideStep('security', 'rules');
|
||||
await apiService.completeGuideStep(testGuide, testGuideManualCompletionStep);
|
||||
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(1);
|
||||
// Verify the completed step now has a "ready_to_complete" status, and the subsequent step is "inactive"
|
||||
expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify({
|
||||
...securityRulesInProgressState,
|
||||
...testGuideStep2InProgressState,
|
||||
steps: [
|
||||
securityRulesInProgressState.steps[0],
|
||||
{
|
||||
id: securityRulesInProgressState.steps[1].id,
|
||||
status: 'ready_to_complete',
|
||||
},
|
||||
{
|
||||
id: securityRulesInProgressState.steps[2].id,
|
||||
status: 'inactive',
|
||||
},
|
||||
testGuideStep2InProgressState.steps[0],
|
||||
{ ...testGuideStep2InProgressState.steps[1], status: 'ready_to_complete' },
|
||||
testGuideStep2InProgressState.steps[2],
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
@ -359,12 +274,8 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
});
|
||||
|
||||
it('does nothing if the step is not in progress', async () => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [searchAddDataActiveState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
|
||||
await apiService.completeGuideStep(searchGuide, firstStep);
|
||||
// by default the state set in beforeEach is test guide, step 1 active
|
||||
await apiService.completeGuideStep(testGuide, testGuideFirstStep);
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
@ -372,11 +283,11 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
describe('isGuidedOnboardingActiveForIntegration$', () => {
|
||||
it('returns true if the integration is part of the active step', (done) => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [securityAddDataInProgressState],
|
||||
state: [testGuideStep1InProgressState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
subscription = apiService
|
||||
.isGuidedOnboardingActiveForIntegration$(endpointIntegration)
|
||||
.isGuidedOnboardingActiveForIntegration$(testIntegration)
|
||||
.subscribe((isIntegrationInGuideStep) => {
|
||||
if (isIntegrationInGuideStep) {
|
||||
done();
|
||||
|
@ -384,13 +295,13 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('returns false if another integration is part of the active step', (done) => {
|
||||
it('returns false if the current step has a different integration', (done) => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [securityAddDataInProgressState],
|
||||
state: [testGuideStep1InProgressState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
subscription = apiService
|
||||
.isGuidedOnboardingActiveForIntegration$(kubernetesIntegration)
|
||||
.isGuidedOnboardingActiveForIntegration$(wrongIntegration)
|
||||
.subscribe((isIntegrationInGuideStep) => {
|
||||
if (!isIntegrationInGuideStep) {
|
||||
done();
|
||||
|
@ -400,11 +311,11 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
|
||||
it('returns false if no guide is active', (done) => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [noGuideActiveState],
|
||||
state: [testGuideNotActiveState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
subscription = apiService
|
||||
.isGuidedOnboardingActiveForIntegration$(endpointIntegration)
|
||||
.isGuidedOnboardingActiveForIntegration$(testIntegration)
|
||||
.subscribe((isIntegrationInGuideStep) => {
|
||||
if (!isIntegrationInGuideStep) {
|
||||
done();
|
||||
|
@ -416,35 +327,35 @@ describe('GuidedOnboarding ApiService', () => {
|
|||
describe('completeGuidedOnboardingForIntegration', () => {
|
||||
it(`completes the step if it's active for the integration`, async () => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [securityAddDataInProgressState],
|
||||
state: [testGuideStep1InProgressState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
|
||||
await apiService.completeGuidedOnboardingForIntegration(endpointIntegration);
|
||||
await apiService.completeGuidedOnboardingForIntegration(testIntegration);
|
||||
expect(httpClient.put).toHaveBeenCalledTimes(1);
|
||||
// this assertion depends on the guides config
|
||||
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
|
||||
body: JSON.stringify(securityRulesActiveState),
|
||||
body: JSON.stringify(testGuideStep2ActiveState),
|
||||
});
|
||||
});
|
||||
|
||||
it(`does nothing if the step has a different integration`, async () => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [securityAddDataInProgressState],
|
||||
state: [testGuideStep1InProgressState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
|
||||
await apiService.completeGuidedOnboardingForIntegration(kubernetesIntegration);
|
||||
await apiService.completeGuidedOnboardingForIntegration(wrongIntegration);
|
||||
expect(httpClient.put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`does nothing if no guide is active`, async () => {
|
||||
httpClient.get.mockResolvedValue({
|
||||
state: [noGuideActiveState],
|
||||
state: [testGuideNotActiveState],
|
||||
});
|
||||
apiService.setup(httpClient);
|
||||
|
||||
await apiService.completeGuidedOnboardingForIntegration(endpointIntegration);
|
||||
await apiService.completeGuidedOnboardingForIntegration(testIntegration);
|
||||
expect(httpClient.put).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -147,10 +147,10 @@ export class ApiService implements GuidedOnboardingApi {
|
|||
});
|
||||
|
||||
const updatedGuide: GuideState = {
|
||||
guideId,
|
||||
isActive: true,
|
||||
status: 'not_started',
|
||||
steps: updatedSteps,
|
||||
guideId,
|
||||
};
|
||||
|
||||
return await this.updateGuideState(updatedGuide, true);
|
||||
|
|
|
@ -6,51 +6,50 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { guidesConfig } from '../constants/guides_config';
|
||||
import { isIntegrationInGuideStep, isLastStep } from './helpers';
|
||||
import {
|
||||
noGuideActiveState,
|
||||
securityAddDataInProgressState,
|
||||
securityRulesActiveState,
|
||||
testGuide,
|
||||
testGuideFirstStep,
|
||||
testGuideLastStep,
|
||||
testGuideNotActiveState,
|
||||
testGuideStep1InProgressState,
|
||||
testGuideStep2InProgressState,
|
||||
testIntegration,
|
||||
wrongIntegration,
|
||||
} from './api.mocks';
|
||||
|
||||
const searchGuide = 'search';
|
||||
const firstStep = guidesConfig[searchGuide].steps[0].id;
|
||||
const lastStep = guidesConfig[searchGuide].steps[guidesConfig[searchGuide].steps.length - 1].id;
|
||||
|
||||
describe('GuidedOnboarding ApiService helpers', () => {
|
||||
// this test suite depends on the guides config
|
||||
describe('isLastStepActive', () => {
|
||||
it('returns true if the passed params are for the last step', () => {
|
||||
const result = isLastStep(searchGuide, lastStep);
|
||||
const result = isLastStep(testGuide, testGuideLastStep);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if the passed params are not for the last step', () => {
|
||||
const result = isLastStep(searchGuide, firstStep);
|
||||
const result = isLastStep(testGuide, testGuideFirstStep);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isIntegrationInGuideStep', () => {
|
||||
it('return true if the integration is defined in the guide step config', () => {
|
||||
const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'endpoint');
|
||||
const result = isIntegrationInGuideStep(testGuideStep1InProgressState, testIntegration);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
it('returns false if a different integration is defined in the guide step', () => {
|
||||
const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'kubernetes');
|
||||
const result = isIntegrationInGuideStep(testGuideStep1InProgressState, wrongIntegration);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
it('returns false if no integration is defined in the guide step', () => {
|
||||
const result = isIntegrationInGuideStep(securityRulesActiveState, 'endpoint');
|
||||
const result = isIntegrationInGuideStep(testGuideStep2InProgressState, testIntegration);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
it('returns false if no guide is active', () => {
|
||||
const result = isIntegrationInGuideStep(noGuideActiveState, 'endpoint');
|
||||
const result = isIntegrationInGuideStep(testGuideNotActiveState, testIntegration);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
it('returns false if no integration passed', () => {
|
||||
const result = isIntegrationInGuideStep(securityAddDataInProgressState);
|
||||
const result = isIntegrationInGuideStep(testGuideStep1InProgressState);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue