From ae79b74076324b7d7273b79d0b77817c89a4580c Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Mon, 3 Oct 2022 20:04:14 -0400
Subject: [PATCH 1/9] create quit modal component
---
.../public/components/guide_panel.tsx | 22 ++++--
.../public/components/quit_guide_modal.tsx | 67 +++++++++++++++++++
2 files changed, 85 insertions(+), 4 deletions(-)
create mode 100644 src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
index bf57d502918d2..c1050d1689882 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
@@ -36,6 +36,7 @@ import type { GuideConfig, StepConfig } from '../types';
import type { ApiService } from '../services/api';
import { GuideStep } from './guide_panel_step';
+import { QuitGuideModal } from './quit_guide_modal';
import { getGuidePanelStyles } from './guide_panel.styles';
interface GuidePanelProps {
@@ -84,6 +85,7 @@ const getProgress = (state?: GuideState): number => {
export const GuidePanel = ({ api, application }: GuidePanelProps) => {
const { euiTheme } = useEuiTheme();
const [isGuideOpen, setIsGuideOpen] = useState(false);
+ const [isQuitGuideModalOpen, setIsQuitGuideModalOpen] = useState(false);
const [guideState, setGuideState] = useState(undefined);
const styles = getGuidePanelStyles(euiTheme);
@@ -108,6 +110,17 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
await api.completeGuide(guideState!.guideId);
};
+ const openQuitGuideModal = () => {
+ // Close the dropdown panel
+ setIsGuideOpen(false);
+ // Open the confirmation modal
+ setIsQuitGuideModalOpen(true);
+ };
+
+ const closeQuitGuideModal = () => {
+ setIsQuitGuideModalOpen(false);
+ };
+
useEffect(() => {
const subscription = api.fetchActiveGuideState$().subscribe((newGuideState) => {
if (newGuideState) {
@@ -236,7 +249,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
- {guideConfig?.steps.map((step, index, steps) => {
+ {guideConfig?.steps.map((step, index) => {
const accordionId = htmlIdGenerator(`accordion${index}`)();
const stepState = guideState?.steps[index];
@@ -271,10 +284,9 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
- {/* TODO: Implement exit guide modal - https://github.com/elastic/kibana/issues/139804 */}
- {}}>
+
{i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', {
- defaultMessage: 'Exit setup guide',
+ defaultMessage: 'Quit setup guide',
})}
@@ -325,6 +337,8 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
)}
+
+ {isQuitGuideModalOpen && }
>
);
};
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
new file mode 100644
index 0000000000000..f4a3741851992
--- /dev/null
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+import React from 'react';
+
+import {
+ EuiModal,
+ EuiModalBody,
+ EuiSpacer,
+ EuiTitle,
+ EuiText,
+ EuiModalFooter,
+ EuiButtonEmpty,
+ EuiButton,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+interface QuitGuideModalProps {
+ closeModal: () => void;
+}
+
+export const QuitGuideModal = ({ closeModal }: QuitGuideModalProps) => {
+ return (
+
+
+
+
+
+ {i18n.translate('guidedOnboarding.quitGuideModal.modalTitle', {
+ defaultMessage: 'Quit this setup guide and discard progress?',
+ })}
+
+
+
+
+
+ {i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', {
+ defaultMessage: 'You can restart anytime, just click Setup guide on the homepage.',
+ })}
+
+
+
+
+
+ {i18n.translate('guidedOnboarding.quitGuideModal.cancelButtonLabel', {
+ defaultMessage: 'Cancel',
+ })}
+
+ {
+ // TODO implement
+ }}
+ fill
+ >
+ {i18n.translate('guidedOnboarding.quitGuideModal.quitButtonLabel', {
+ defaultMessage: 'Quit guide',
+ })}
+
+
+
+ );
+};
From c5a1fb4b32caf776f383a360e9dd09206f37be08 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 4 Oct 2022 13:46:45 -0400
Subject: [PATCH 2/9] add delete api
---
.../public/components/guide_panel.tsx | 16 +++++---
.../public/components/quit_guide_modal.tsx | 38 +++++++++++++-----
.../guided_onboarding/public/plugin.tsx | 8 +++-
.../guided_onboarding/public/services/api.ts | 29 +++++++++++++-
.../guided_onboarding/server/routes/index.ts | 39 ++++++++++++++++++-
5 files changed, 110 insertions(+), 20 deletions(-)
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
index c1050d1689882..5cd04c39d7706 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
@@ -29,6 +29,7 @@ import {
import { ApplicationStart } from '@kbn/core-application-browser';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
+import type { NotificationsSetup } from '@kbn/core/public';
import { guidesConfig } from '../constants/guides_config';
import type { GuideState, GuideStepIds } from '../../common/types';
import type { GuideConfig, StepConfig } from '../types';
@@ -42,6 +43,7 @@ import { getGuidePanelStyles } from './guide_panel.styles';
interface GuidePanelProps {
api: ApiService;
application: ApplicationStart;
+ notifications: NotificationsSetup;
}
const getConfig = (state?: GuideState): GuideConfig | undefined => {
@@ -82,7 +84,7 @@ const getProgress = (state?: GuideState): number => {
return 0;
};
-export const GuidePanel = ({ api, application }: GuidePanelProps) => {
+export const GuidePanel = ({ api, application, notifications }: GuidePanelProps) => {
const { euiTheme } = useEuiTheme();
const [isGuideOpen, setIsGuideOpen] = useState(false);
const [isQuitGuideModalOpen, setIsQuitGuideModalOpen] = useState(false);
@@ -123,9 +125,7 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
useEffect(() => {
const subscription = api.fetchActiveGuideState$().subscribe((newGuideState) => {
- if (newGuideState) {
- setGuideState(newGuideState);
- }
+ setGuideState(newGuideState);
});
return () => subscription.unsubscribe();
}, [api]);
@@ -338,7 +338,13 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
)}
- {isQuitGuideModalOpen && }
+ {isQuitGuideModalOpen && (
+
+ )}
>
);
};
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index f4a3741851992..c06f8216107e9 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { useState } from 'react';
import {
EuiModal,
@@ -18,12 +18,38 @@ import {
EuiButton,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import type { NotificationsSetup } from '@kbn/core-notifications-browser';
+import { GuideId } from '../../common/types';
+import { apiService } from '../services/api';
interface QuitGuideModalProps {
closeModal: () => void;
+ currentGuide: GuideId;
+ notifications: NotificationsSetup;
}
-export const QuitGuideModal = ({ closeModal }: QuitGuideModalProps) => {
+export const QuitGuideModal = ({
+ closeModal,
+ currentGuide,
+ notifications,
+}: QuitGuideModalProps) => {
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ const deleteGuide = async () => {
+ setIsDeleting(true);
+ const { error } = await apiService.deleteGuide(currentGuide);
+ setIsDeleting(false);
+
+ if (error) {
+ notifications.toasts.addError(error, {
+ title: i18n.translate('guidedOnboarding.quitGuideModal.errorToastTitle', {
+ defaultMessage: 'There was an error quitting the guide. Please try again.',
+ }),
+ });
+ }
+ closeModal();
+ };
+
return (
@@ -50,13 +76,7 @@ export const QuitGuideModal = ({ closeModal }: QuitGuideModalProps) => {
defaultMessage: 'Cancel',
})}
- {
- // TODO implement
- }}
- fill
- >
+
{i18n.translate('guidedOnboarding.quitGuideModal.quitButtonLabel', {
defaultMessage: 'Quit guide',
})}
diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx
index f74e19a03300f..b6118d318dc47 100755
--- a/src/plugins/guided_onboarding/public/plugin.tsx
+++ b/src/plugins/guided_onboarding/public/plugin.tsx
@@ -17,6 +17,7 @@ import {
CoreTheme,
ApplicationStart,
PluginInitializerContext,
+ NotificationsSetup,
} from '@kbn/core/public';
import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
@@ -42,7 +43,7 @@ export class GuidedOnboardingPlugin
return {};
}
- const { chrome, http, theme, application } = core;
+ const { chrome, http, theme, application, notifications } = core;
// Initialize services
apiService.setup(http);
@@ -55,6 +56,7 @@ export class GuidedOnboardingPlugin
theme$: theme.theme$,
api: apiService,
application,
+ notifications,
}),
});
@@ -71,16 +73,18 @@ export class GuidedOnboardingPlugin
theme$,
api,
application,
+ notifications,
}: {
targetDomElement: HTMLElement;
theme$: Rx.Observable;
api: ApiService;
application: ApplicationStart;
+ notifications: NotificationsSetup;
}) {
ReactDOM.render(
-
+
,
targetDomElement
diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts
index 1adfaa5d8cc23..67e6a728786d5 100644
--- a/src/plugins/guided_onboarding/public/services/api.ts
+++ b/src/plugins/guided_onboarding/public/services/api.ts
@@ -15,6 +15,7 @@ import { isLastStep, getGuideConfig } from './helpers';
export class ApiService {
private client: HttpSetup | undefined;
+ private isGuideAbandoned: boolean = false;
private onboardingGuideState$!: BehaviorSubject;
public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false);
@@ -32,7 +33,7 @@ export class ApiService {
// TODO add error handling if this.client has not been initialized or request fails
return this.onboardingGuideState$.pipe(
concatMap((state) =>
- state === undefined
+ this.isGuideAbandoned === false && state === undefined
? from(
this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, {
query: {
@@ -70,6 +71,30 @@ export class ApiService {
}
}
+ /**
+ * Async operation to delete a guide
+ * On the server, the SO is deleted for the selected guide ID
+ * This is used for the "Quit guide" functionality on the dropdown panel
+ * @param {GuideId} guideId the id of the guide (one of search, observability, security)
+ * @return {Promise} a promise with the response or error
+ */
+ public async deleteGuide(guideId: GuideId): Promise<{ response?: GuideState; error?: Error }> {
+ if (!this.client) {
+ throw new Error('ApiService has not be initialized.');
+ }
+
+ try {
+ const response = await this.client.delete(`${API_BASE_PATH}/state/${guideId}`);
+ // Mark the guide as abandoned
+ this.isGuideAbandoned = true;
+ // Reset the guide state
+ this.onboardingGuideState$.next(undefined);
+ return { response };
+ } catch (error) {
+ return { error };
+ }
+ }
+
/**
* Updates the SO with the updated guide state and refreshes the observables
* This is largely used internally and for tests
@@ -102,7 +127,7 @@ export class ApiService {
/**
* Activates a guide by guideId
* This is useful for the onboarding landing page, when a user selects a guide to start or continue
- * @param {GuideId} guideID the id of the guide (one of search, observability, security)
+ * @param {GuideId} guideId the id of the guide (one of search, observability, security)
* @param {GuideState} guideState (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide)
* @return {Promise} a promise with the updated guide state
*/
diff --git a/src/plugins/guided_onboarding/server/routes/index.ts b/src/plugins/guided_onboarding/server/routes/index.ts
index cce5aad08b1e5..d7339d14e98a5 100755
--- a/src/plugins/guided_onboarding/server/routes/index.ts
+++ b/src/plugins/guided_onboarding/server/routes/index.ts
@@ -8,6 +8,7 @@
import { schema } from '@kbn/config-schema';
import type { IRouter, SavedObjectsClient } from '@kbn/core/server';
+import { API_BASE_PATH } from '../../common/constants';
import type { GuideState } from '../../common/types';
import { guidedSetupSavedObjectsType } from '../saved_objects';
@@ -35,7 +36,7 @@ export function defineRoutes(router: IRouter) {
// Fetch all guides state; optionally pass the query param ?active=true to only return the active guide
router.get(
{
- path: '/api/guided_onboarding/state',
+ path: `${API_BASE_PATH}/state`,
validate: {
query: schema.object({
active: schema.maybe(schema.boolean()),
@@ -69,7 +70,7 @@ export function defineRoutes(router: IRouter) {
// will also check any existing active guides and update them to an "inactive" state
router.put(
{
- path: '/api/guided_onboarding/state',
+ path: `${API_BASE_PATH}/state`,
validate: {
body: schema.object({
status: schema.string(),
@@ -160,4 +161,38 @@ export function defineRoutes(router: IRouter) {
}
}
);
+
+ // Delete SO for selected guide
+ router.delete(
+ {
+ path: `${API_BASE_PATH}/state/{guideId}`,
+ validate: {
+ params: schema.object({
+ guideId: schema.string(),
+ }),
+ },
+ },
+ async (context, request, response) => {
+ const coreContext = await context.core;
+ const { guideId } = request.params;
+ const soClient = coreContext.savedObjects.client as SavedObjectsClient;
+
+ const existingGuideSO = await findGuideById(soClient, guideId);
+
+ if (existingGuideSO.total > 0) {
+ const existingGuide = existingGuideSO.saved_objects[0];
+
+ await soClient.delete(guidedSetupSavedObjectsType, existingGuide.id);
+
+ return response.ok({
+ body: {},
+ });
+ } else {
+ // In the case that the SO doesn't exist (unlikely), return successful response
+ return response.ok({
+ body: {},
+ });
+ }
+ }
+ );
}
From 0577a89a80694e53b280d94dea73156c0057fc22 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 4 Oct 2022 15:46:02 -0400
Subject: [PATCH 3/9] add tests
---
.../public/components/guide_panel.test.tsx | 57 ++++++++++++++++++-
.../public/components/guide_panel.tsx | 2 +-
.../public/components/quit_guide_modal.tsx | 19 +++++--
.../public/services/api.test.ts | 8 +++
4 files changed, 79 insertions(+), 7 deletions(-)
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
index 3506c15fcba35..93ea8005ea259 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
@@ -10,7 +10,7 @@ import { act } from 'react-dom/test-utils';
import React from 'react';
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
-import { httpServiceMock } from '@kbn/core/public/mocks';
+import { httpServiceMock, notificationServiceMock } from '@kbn/core/public/mocks';
import { HttpSetup } from '@kbn/core/public';
import { guidesConfig } from '../constants/guides_config';
@@ -20,6 +20,7 @@ import { GuidePanel } from './guide_panel';
import { registerTestBed, TestBed } from '@kbn/test-jest-helpers';
const applicationMock = applicationServiceMock.createStartContract();
+const notificationsMock = notificationServiceMock.createSetupContract();
const mockActiveSearchGuideState: GuideState = {
guideId: 'search',
@@ -42,7 +43,9 @@ const mockActiveSearchGuideState: GuideState = {
};
const getGuidePanel = () => () => {
- return ;
+ return (
+
+ );
};
describe('Guided setup', () => {
@@ -55,6 +58,7 @@ describe('Guided setup', () => {
httpClient.get.mockResolvedValue({
state: [],
});
+ httpClient.delete.mockResolvedValue({});
apiService.setup(httpClient);
await act(async () => {
@@ -228,5 +232,54 @@ describe('Guided setup', () => {
expect(find('activeStepButtonLabel').text()).toEqual('Continue');
});
});
+
+ describe('Quit guide modal', () => {
+ beforeEach(async () => {
+ const { component, find, exists } = testBed;
+
+ await act(async () => {
+ // Enable the "search" guide
+ await apiService.updateGuideState(mockActiveSearchGuideState, true);
+ });
+
+ component.update();
+
+ await act(async () => {
+ find('quitGuideButton').simulate('click');
+ });
+
+ component.update();
+
+ expect(exists('quitGuideModal')).toBe(true);
+ });
+
+ test('quit a guide', async () => {
+ const { component, find, exists } = testBed;
+
+ await act(async () => {
+ find('confirmQuitGuideButton').simulate('click');
+ });
+
+ component.update();
+
+ expect(exists('quitGuideModal')).toBe(false);
+ // For now, the guide button is disabled once a user quits a guide
+ // This behavior will change once https://github.com/elastic/kibana/issues/141129 is implemented
+ expect(exists('disabledGuideButton')).toBe(true);
+ });
+
+ test('cancels out of the quit guide confirmation modal', async () => {
+ const { component, find, exists } = testBed;
+
+ await act(async () => {
+ find('cancelQuitGuideButton').simulate('click');
+ });
+
+ component.update();
+
+ expect(exists('quitGuideModal')).toBe(false);
+ expect(exists('guideButton')).toBe(true);
+ });
+ });
});
});
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
index 5cd04c39d7706..d59e424cab025 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
@@ -284,7 +284,7 @@ export const GuidePanel = ({ api, application, notifications }: GuidePanelProps)
-
+
{i18n.translate('guidedOnboarding.dropdownPanel.footer.exitGuideButtonLabel', {
defaultMessage: 'Quit setup guide',
})}
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index c06f8216107e9..c8d59565b54c9 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -38,9 +38,9 @@ export const QuitGuideModal = ({
const deleteGuide = async () => {
setIsDeleting(true);
const { error } = await apiService.deleteGuide(currentGuide);
- setIsDeleting(false);
if (error) {
+ setIsDeleting(false);
notifications.toasts.addError(error, {
title: i18n.translate('guidedOnboarding.quitGuideModal.errorToastTitle', {
defaultMessage: 'There was an error quitting the guide. Please try again.',
@@ -51,7 +51,12 @@ export const QuitGuideModal = ({
};
return (
-
+
@@ -71,12 +76,18 @@ export const QuitGuideModal = ({
-
+
{i18n.translate('guidedOnboarding.quitGuideModal.cancelButtonLabel', {
defaultMessage: 'Cancel',
})}
-
+
{i18n.translate('guidedOnboarding.quitGuideModal.quitButtonLabel', {
defaultMessage: 'Quit guide',
})}
diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts
index ffe5596bd7e35..7f4cbd685c364 100644
--- a/src/plugins/guided_onboarding/public/services/api.test.ts
+++ b/src/plugins/guided_onboarding/public/services/api.test.ts
@@ -84,6 +84,14 @@ describe('GuidedOnboarding ApiService', () => {
});
});
+ describe('deleteGuide', () => {
+ it('sends a request to the delete API', async () => {
+ await apiService.deleteGuide(searchGuide);
+ expect(httpClient.delete).toHaveBeenCalledTimes(1);
+ expect(httpClient.delete).toHaveBeenCalledWith(`${API_BASE_PATH}/state/${searchGuide}`);
+ });
+ });
+
describe('updateGuideState', () => {
it('sends a request to the put API', async () => {
const updatedState: GuideState = {
From d529025a092ea9112f65dc9fa909f3d0785a8956 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 4 Oct 2022 21:10:51 -0400
Subject: [PATCH 4/9] Update
src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
Co-authored-by: Kelly Murphy
---
.../guided_onboarding/public/components/quit_guide_modal.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index c8d59565b54c9..9925ca3356fa6 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -62,7 +62,7 @@ export const QuitGuideModal = ({
{i18n.translate('guidedOnboarding.quitGuideModal.modalTitle', {
- defaultMessage: 'Quit this setup guide and discard progress?',
+ defaultMessage: 'Quit this guide and discard progress?',
})}
From ba4036fd36db672258e0163a306fdd1df09e6c03 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Tue, 4 Oct 2022 21:13:47 -0400
Subject: [PATCH 5/9] Update
src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
Co-authored-by: Kelly Murphy
---
.../guided_onboarding/public/components/quit_guide_modal.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index 9925ca3356fa6..fc806136d6744 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -70,7 +70,7 @@ export const QuitGuideModal = ({
{i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', {
- defaultMessage: 'You can restart anytime, just click Setup guide on the homepage.',
+ defaultMessage: 'You can restart anytime by opening the Setup guide from the Help menu.',
})}
From d45c83d23f8da899c4b8594d2bdc2970b85f000e Mon Sep 17 00:00:00 2001
From: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Date: Wed, 5 Oct 2022 01:21:38 +0000
Subject: [PATCH 6/9] [CI] Auto-commit changed files from 'node
scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
---
.../guided_onboarding/public/components/quit_guide_modal.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index fc806136d6744..34e5a2a857f7e 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -70,7 +70,8 @@ export const QuitGuideModal = ({
{i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', {
- defaultMessage: 'You can restart anytime by opening the Setup guide from the Help menu.',
+ defaultMessage:
+ 'You can restart anytime by opening the Setup guide from the Help menu.',
})}
From 57dcbcc16699bc9f67c456e7bf6a32a3856de2fc Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Wed, 5 Oct 2022 09:12:48 -0400
Subject: [PATCH 7/9] cleanup
---
.../public/components/quit_guide_modal.tsx | 73 ++++++-------------
.../guided_onboarding/public/services/api.ts | 8 +-
.../guided_onboarding/server/routes/index.ts | 8 +-
3 files changed, 35 insertions(+), 54 deletions(-)
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index 34e5a2a857f7e..7cd365c4988c5 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -7,16 +7,7 @@
*/
import React, { useState } from 'react';
-import {
- EuiModal,
- EuiModalBody,
- EuiSpacer,
- EuiTitle,
- EuiText,
- EuiModalFooter,
- EuiButtonEmpty,
- EuiButton,
-} from '@elastic/eui';
+import { EuiText, EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { NotificationsSetup } from '@kbn/core-notifications-browser';
import { GuideId } from '../../common/types';
@@ -51,49 +42,31 @@ export const QuitGuideModal = ({
};
return (
-
-
-
-
-
- {i18n.translate('guidedOnboarding.quitGuideModal.modalTitle', {
- defaultMessage: 'Quit this guide and discard progress?',
- })}
-
-
-
-
-
- {i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', {
- defaultMessage:
- 'You can restart anytime by opening the Setup guide from the Help menu.',
- })}
-
-
-
-
-
- {i18n.translate('guidedOnboarding.quitGuideModal.cancelButtonLabel', {
- defaultMessage: 'Cancel',
+
+
+ {i18n.translate('guidedOnboarding.quitGuideModal.modalDescription', {
+ defaultMessage:
+ 'You can restart anytime by opening the Setup guide from the Help menu.',
})}
-
-
- {i18n.translate('guidedOnboarding.quitGuideModal.quitButtonLabel', {
- defaultMessage: 'Quit guide',
- })}
-
-
-
+
+
+
);
};
diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts
index 9a8edafaf3614..09510ffa0b507 100644
--- a/src/plugins/guided_onboarding/public/services/api.ts
+++ b/src/plugins/guided_onboarding/public/services/api.ts
@@ -84,13 +84,17 @@ export class ApiService implements GuidedOnboardingApi {
* @param {GuideId} guideId the id of the guide (one of search, observability, security)
* @return {Promise} a promise with the response or error
*/
- public async deleteGuide(guideId: GuideId): Promise<{ response?: GuideState; error?: Error }> {
+ public async deleteGuide(
+ guideId: GuideId
+ ): Promise<{ response?: { deletedGuide: GuideId }; error?: Error }> {
if (!this.client) {
throw new Error('ApiService has not be initialized.');
}
try {
- const response = await this.client.delete(`${API_BASE_PATH}/state/${guideId}`);
+ const response = await this.client.delete<{ deletedGuide: GuideId }>(
+ `${API_BASE_PATH}/state/${guideId}`
+ );
// Mark the guide as abandoned
this.isGuideAbandoned = true;
// Reset the guide state
diff --git a/src/plugins/guided_onboarding/server/routes/index.ts b/src/plugins/guided_onboarding/server/routes/index.ts
index d7339d14e98a5..a8b60dc0500e7 100755
--- a/src/plugins/guided_onboarding/server/routes/index.ts
+++ b/src/plugins/guided_onboarding/server/routes/index.ts
@@ -185,12 +185,16 @@ export function defineRoutes(router: IRouter) {
await soClient.delete(guidedSetupSavedObjectsType, existingGuide.id);
return response.ok({
- body: {},
+ body: {
+ deletedGuide: guideId,
+ },
});
} else {
// In the case that the SO doesn't exist (unlikely), return successful response
return response.ok({
- body: {},
+ body: {
+ deletedGuide: guideId,
+ },
});
}
}
From 8e59be984a985dbec68ef3e1377f5e9faf88bfb0 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Wed, 5 Oct 2022 09:20:49 -0400
Subject: [PATCH 8/9] fix tests
---
.../guided_onboarding/public/components/guide_panel.test.tsx | 4 ++--
.../guided_onboarding/public/components/quit_guide_modal.tsx | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
index 93ea8005ea259..1bee057beb4a7 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
@@ -257,7 +257,7 @@ describe('Guided setup', () => {
const { component, find, exists } = testBed;
await act(async () => {
- find('confirmQuitGuideButton').simulate('click');
+ find('confirmModalConfirmButton').simulate('click');
});
component.update();
@@ -272,7 +272,7 @@ describe('Guided setup', () => {
const { component, find, exists } = testBed;
await act(async () => {
- find('cancelQuitGuideButton').simulate('click');
+ find('confirmModalCancelButton').simulate('click');
});
component.update();
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index 7cd365c4988c5..4a7beeb048513 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -58,6 +58,7 @@ export const QuitGuideModal = ({
aria-label="quitGuideModal"
buttonColor="warning"
isLoading={isDeleting}
+ data-test-subj="quitGuideModal"
>
From dc79bcea9ea7c93c5ba09d41b3774ace514cbfaa Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Mon, 10 Oct 2022 09:31:55 -0400
Subject: [PATCH 9/9] address review feedback
---
.../public/components/guide_panel.test.tsx | 8 +--
.../public/components/guide_panel.tsx | 11 ++--
.../public/components/quit_guide_modal.tsx | 25 ++--------
.../guided_onboarding/public/plugin.tsx | 8 +--
.../public/services/api.test.ts | 16 ++++--
.../guided_onboarding/public/services/api.ts | 50 +++++++------------
.../guided_onboarding/server/routes/index.ts | 38 --------------
7 files changed, 42 insertions(+), 114 deletions(-)
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
index 1bee057beb4a7..5bd846aebbdef 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx
@@ -10,7 +10,7 @@ import { act } from 'react-dom/test-utils';
import React from 'react';
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
-import { httpServiceMock, notificationServiceMock } from '@kbn/core/public/mocks';
+import { httpServiceMock } from '@kbn/core/public/mocks';
import { HttpSetup } from '@kbn/core/public';
import { guidesConfig } from '../constants/guides_config';
@@ -20,7 +20,6 @@ import { GuidePanel } from './guide_panel';
import { registerTestBed, TestBed } from '@kbn/test-jest-helpers';
const applicationMock = applicationServiceMock.createStartContract();
-const notificationsMock = notificationServiceMock.createSetupContract();
const mockActiveSearchGuideState: GuideState = {
guideId: 'search',
@@ -43,9 +42,7 @@ const mockActiveSearchGuideState: GuideState = {
};
const getGuidePanel = () => () => {
- return (
-
- );
+ return ;
};
describe('Guided setup', () => {
@@ -58,7 +55,6 @@ describe('Guided setup', () => {
httpClient.get.mockResolvedValue({
state: [],
});
- httpClient.delete.mockResolvedValue({});
apiService.setup(httpClient);
await act(async () => {
diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
index d59e424cab025..7c122492d84a1 100644
--- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx
@@ -29,7 +29,7 @@ import {
import { ApplicationStart } from '@kbn/core-application-browser';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
-import type { NotificationsSetup } from '@kbn/core/public';
+
import { guidesConfig } from '../constants/guides_config';
import type { GuideState, GuideStepIds } from '../../common/types';
import type { GuideConfig, StepConfig } from '../types';
@@ -43,7 +43,6 @@ import { getGuidePanelStyles } from './guide_panel.styles';
interface GuidePanelProps {
api: ApiService;
application: ApplicationStart;
- notifications: NotificationsSetup;
}
const getConfig = (state?: GuideState): GuideConfig | undefined => {
@@ -84,7 +83,7 @@ const getProgress = (state?: GuideState): number => {
return 0;
};
-export const GuidePanel = ({ api, application, notifications }: GuidePanelProps) => {
+export const GuidePanel = ({ api, application }: GuidePanelProps) => {
const { euiTheme } = useEuiTheme();
const [isGuideOpen, setIsGuideOpen] = useState(false);
const [isQuitGuideModalOpen, setIsQuitGuideModalOpen] = useState(false);
@@ -339,11 +338,7 @@ export const GuidePanel = ({ api, application, notifications }: GuidePanelProps)
)}
{isQuitGuideModalOpen && (
-
+
)}
>
);
diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
index 4a7beeb048513..a7a7e34c311b4 100644
--- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
+++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx
@@ -9,35 +9,20 @@ import React, { useState } from 'react';
import { EuiText, EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import type { NotificationsSetup } from '@kbn/core-notifications-browser';
-import { GuideId } from '../../common/types';
+import { GuideState } from '../../common/types';
import { apiService } from '../services/api';
interface QuitGuideModalProps {
closeModal: () => void;
- currentGuide: GuideId;
- notifications: NotificationsSetup;
+ currentGuide: GuideState;
}
-export const QuitGuideModal = ({
- closeModal,
- currentGuide,
- notifications,
-}: QuitGuideModalProps) => {
+export const QuitGuideModal = ({ closeModal, currentGuide }: QuitGuideModalProps) => {
const [isDeleting, setIsDeleting] = useState(false);
const deleteGuide = async () => {
setIsDeleting(true);
- const { error } = await apiService.deleteGuide(currentGuide);
-
- if (error) {
- setIsDeleting(false);
- notifications.toasts.addError(error, {
- title: i18n.translate('guidedOnboarding.quitGuideModal.errorToastTitle', {
- defaultMessage: 'There was an error quitting the guide. Please try again.',
- }),
- });
- }
+ await apiService.deactivateGuide(currentGuide);
closeModal();
};
@@ -45,7 +30,7 @@ export const QuitGuideModal = ({
;
api: ApiService;
application: ApplicationStart;
- notifications: NotificationsSetup;
}) {
ReactDOM.render(
-
+
,
targetDomElement
diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts
index f116a6af153e7..5deb3d50987f2 100644
--- a/src/plugins/guided_onboarding/public/services/api.test.ts
+++ b/src/plugins/guided_onboarding/public/services/api.test.ts
@@ -72,11 +72,17 @@ describe('GuidedOnboarding ApiService', () => {
});
});
- describe('deleteGuide', () => {
- it('sends a request to the delete API', async () => {
- await apiService.deleteGuide(searchGuide);
- expect(httpClient.delete).toHaveBeenCalledTimes(1);
- expect(httpClient.delete).toHaveBeenCalledWith(`${API_BASE_PATH}/state/${searchGuide}`);
+ describe('deactivateGuide', () => {
+ it('deactivates an existing guide', async () => {
+ await apiService.deactivateGuide(searchAddDataActiveState);
+
+ expect(httpClient.put).toHaveBeenCalledTimes(1);
+ expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
+ body: JSON.stringify({
+ ...searchAddDataActiveState,
+ isActive: false,
+ }),
+ });
});
});
diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts
index 09510ffa0b507..7c970717be5f1 100644
--- a/src/plugins/guided_onboarding/public/services/api.ts
+++ b/src/plugins/guided_onboarding/public/services/api.ts
@@ -21,7 +21,6 @@ import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/
export class ApiService implements GuidedOnboardingApi {
private client: HttpSetup | undefined;
- private isGuideAbandoned: boolean = false;
private onboardingGuideState$!: BehaviorSubject;
public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false);
@@ -39,7 +38,7 @@ export class ApiService implements GuidedOnboardingApi {
// TODO add error handling if this.client has not been initialized or request fails
return this.onboardingGuideState$.pipe(
concatMap((state) =>
- this.isGuideAbandoned === false && state === undefined
+ state === undefined
? from(
this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, {
query: {
@@ -77,34 +76,6 @@ export class ApiService implements GuidedOnboardingApi {
}
}
- /**
- * Async operation to delete a guide
- * On the server, the SO is deleted for the selected guide ID
- * This is used for the "Quit guide" functionality on the dropdown panel
- * @param {GuideId} guideId the id of the guide (one of search, observability, security)
- * @return {Promise} a promise with the response or error
- */
- public async deleteGuide(
- guideId: GuideId
- ): Promise<{ response?: { deletedGuide: GuideId }; error?: Error }> {
- if (!this.client) {
- throw new Error('ApiService has not be initialized.');
- }
-
- try {
- const response = await this.client.delete<{ deletedGuide: GuideId }>(
- `${API_BASE_PATH}/state/${guideId}`
- );
- // Mark the guide as abandoned
- this.isGuideAbandoned = true;
- // Reset the guide state
- this.onboardingGuideState$.next(undefined);
- return { response };
- } catch (error) {
- return { error };
- }
- }
-
/**
* Updates the SO with the updated guide state and refreshes the observables
* This is largely used internally and for tests
@@ -124,7 +95,8 @@ export class ApiService implements GuidedOnboardingApi {
const response = await this.client.put<{ state: GuideState }>(`${API_BASE_PATH}/state`, {
body: JSON.stringify(newState),
});
- this.onboardingGuideState$.next(newState);
+ // If the guide has been deactivated, we return undefined
+ this.onboardingGuideState$.next(newState.isActive ? newState : undefined);
this.isGuidePanelOpen$.next(panelState);
return response;
} catch (error) {
@@ -181,6 +153,22 @@ 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)
+ * @return {Promise} a promise with the updated guide state
+ */
+ public async deactivateGuide(guide: GuideState): Promise<{ state: GuideState } | undefined> {
+ return await this.updateGuideState(
+ {
+ ...guide,
+ isActive: false,
+ },
+ false
+ );
+ }
+
/**
* Completes a guide
* Updates the overall guide status to 'complete', and marks it as inactive
diff --git a/src/plugins/guided_onboarding/server/routes/index.ts b/src/plugins/guided_onboarding/server/routes/index.ts
index a8b60dc0500e7..adc65d0bf6866 100755
--- a/src/plugins/guided_onboarding/server/routes/index.ts
+++ b/src/plugins/guided_onboarding/server/routes/index.ts
@@ -161,42 +161,4 @@ export function defineRoutes(router: IRouter) {
}
}
);
-
- // Delete SO for selected guide
- router.delete(
- {
- path: `${API_BASE_PATH}/state/{guideId}`,
- validate: {
- params: schema.object({
- guideId: schema.string(),
- }),
- },
- },
- async (context, request, response) => {
- const coreContext = await context.core;
- const { guideId } = request.params;
- const soClient = coreContext.savedObjects.client as SavedObjectsClient;
-
- const existingGuideSO = await findGuideById(soClient, guideId);
-
- if (existingGuideSO.total > 0) {
- const existingGuide = existingGuideSO.saved_objects[0];
-
- await soClient.delete(guidedSetupSavedObjectsType, existingGuide.id);
-
- return response.ok({
- body: {
- deletedGuide: guideId,
- },
- });
- } else {
- // In the case that the SO doesn't exist (unlikely), return successful response
- return response.ok({
- body: {
- deletedGuide: guideId,
- },
- });
- }
- }
- );
}