Skip to content

Commit

Permalink
add delete api
Browse files Browse the repository at this point in the history
  • Loading branch information
alisonelizabeth committed Oct 4, 2022
1 parent ae79b74 commit c5a1fb4
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 20 deletions.
16 changes: 11 additions & 5 deletions src/plugins/guided_onboarding/public/components/guide_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -42,6 +43,7 @@ import { getGuidePanelStyles } from './guide_panel.styles';
interface GuidePanelProps {
api: ApiService;
application: ApplicationStart;
notifications: NotificationsSetup;
}

const getConfig = (state?: GuideState): GuideConfig | undefined => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -338,7 +338,13 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => {
</EuiFlyout>
)}

{isQuitGuideModalOpen && <QuitGuideModal closeModal={closeQuitGuideModal} />}
{isQuitGuideModalOpen && (
<QuitGuideModal
closeModal={closeQuitGuideModal}
currentGuide={guideState!.guideId}
notifications={notifications}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<boolean>(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 (
<EuiModal maxWidth={448} aria-label="quitGuideModal" onClose={closeModal}>
<EuiModalBody>
Expand All @@ -50,13 +76,7 @@ export const QuitGuideModal = ({ closeModal }: QuitGuideModalProps) => {
defaultMessage: 'Cancel',
})}
</EuiButtonEmpty>
<EuiButton
color="warning"
onClick={() => {
// TODO implement
}}
fill
>
<EuiButton color="warning" isLoading={isDeleting} onClick={deleteGuide} fill>
{i18n.translate('guidedOnboarding.quitGuideModal.quitButtonLabel', {
defaultMessage: 'Quit guide',
})}
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/guided_onboarding/public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CoreTheme,
ApplicationStart,
PluginInitializerContext,
NotificationsSetup,
} from '@kbn/core/public';

import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
Expand All @@ -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);
Expand All @@ -55,6 +56,7 @@ export class GuidedOnboardingPlugin
theme$: theme.theme$,
api: apiService,
application,
notifications,
}),
});

Expand All @@ -71,16 +73,18 @@ export class GuidedOnboardingPlugin
theme$,
api,
application,
notifications,
}: {
targetDomElement: HTMLElement;
theme$: Rx.Observable<CoreTheme>;
api: ApiService;
application: ApplicationStart;
notifications: NotificationsSetup;
}) {
ReactDOM.render(
<KibanaThemeProvider theme$={theme$}>
<I18nProvider>
<GuidePanel api={api} application={application} />
<GuidePanel api={api} application={application} notifications={notifications} />
</I18nProvider>
</KibanaThemeProvider>,
targetDomElement
Expand Down
29 changes: 27 additions & 2 deletions src/plugins/guided_onboarding/public/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { isLastStep, getGuideConfig } from './helpers';

export class ApiService {
private client: HttpSetup | undefined;
private isGuideAbandoned: boolean = false;
private onboardingGuideState$!: BehaviorSubject<GuideState | undefined>;
public isGuidePanelOpen$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

Expand All @@ -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: {
Expand Down Expand Up @@ -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<GuideState>(`${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
Expand Down Expand Up @@ -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
*/
Expand Down
39 changes: 37 additions & 2 deletions src/plugins/guided_onboarding/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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()),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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: {},
});
}
}
);
}

0 comments on commit c5a1fb4

Please sign in to comment.