Skip to content

Commit

Permalink
Merge branch 'main' into cases-files-be
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathan-buttner authored Feb 28, 2023
2 parents 9828325 + a87f440 commit d4dcc78
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 46 deletions.
16 changes: 14 additions & 2 deletions src/plugins/guided_onboarding/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages.

The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API. The server-side code is not intended for external use.
The guided onboarding plugin includes a client-side code for the UI and the server-side code for the internal API.

The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins.
The client-side code registers a button in the Kibana header that controls the guided onboarding panel (checklist) depending on the current state. There is also an API service exposed from the client-side start contract. The API service is intended for external use by other plugins that need to react to the guided onboarding state, for example hide or display UI elements if a guide step is active.

Besides the internal API routes, the server-side code also exposes a function to register guide configs from the server-side setup start contract. This function is intended for external use by any plugin that need to add a new guide or modify an existing one.

---
## Current functionality
Expand Down Expand Up @@ -106,3 +108,13 @@ The guided onboarding exposes a function `registerGuideConfig(guideId: GuideId,
- observability: `x-pack/plugins/observability/server/plugin.ts`
- security solution: `x-pack/plugins/security_solution/server/plugin.ts`


## Adding a new guide
Follow these simple steps to add a new guide to the guided onboarding framework. For more detailed information about framework functionality and architecture and about API services exposed by the plugin, please read the full readme.

1. Declare the `guidedOnboarding` plugin as a dependency in your plugin's `kibana.json` file. Add the guided onboarding plugin's client-side start contract to your plugin's client-side start dependencies and the guided onboarding plugin's server-side setup contract to your plugin's server-side dependencies.
2. Define the configuration for your guide. At a high level, this includes a title, description, and list of steps. See this [example config](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/common/test_guide_config.ts) or consult the `GuideConfig` interface.
3. Register your guide during your plugin's server-side setup by calling a function exposed by the guided onboarding plugin: `registerGuideConfig(guideId: GuideId, guideConfig: GuideConfig)`. For an example, see this [example plugin](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/server/plugin.ts).
4. Update the cards on the landing page to include your guide in the use case selection. Make sure that the card doesn't have the property `navigateTo` because that is only used for cards that redirect to Kibana pages and don't start a guide. Also add the same value to the property `guideId` as used in the guide config. Landing page cards are configured in this [kbn-guided-onboarding package](https://github.com/elastic/kibana/blob/main/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.tsx).
5. Integrate the new guide into your Kibana pages by using the guided onboarding client-side API service. Make sure your Kibana pages correctly display UI elements depending on the active guide step and the UI flow is straight forward to complete the guide. See existing guides for an example and read more about the API service in this file, the section "Client-side: API service".
6. Optionally, update the example plugin's [form](https://github.com/elastic/kibana/blob/main/examples/guided_onboarding_example/public/components/main.tsx#L38) to be able to start your guide from that page and activate any step in your guide (useful to test your guide steps).
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as React from 'react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { IToasts } from '@kbn/core/public';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { UpdateApiKeyModalConfirmation } from './update_api_key_modal_confirmation';
import { useKibana } from '../../common/lib/kibana';

const Providers = ({ children }: { children: any }) => (
<IntlProvider locale="en">{children}</IntlProvider>
);

const renderWithProviders = (ui: any) => {
return render(ui, { wrapper: Providers });
};

jest.mock('../../common/lib/kibana');
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;

describe('Update Api Key', () => {
const onCancel = jest.fn();
const apiUpdateApiKeyCall = jest.fn();
const setIsLoadingState = jest.fn();
const onUpdated = jest.fn();
const onSearchPopulate = jest.fn();

const addSuccess = jest.fn();
const addError = jest.fn();

beforeAll(() => {
useKibanaMock().services.notifications.toasts = {
addSuccess,
addError,
} as unknown as IToasts;
});

afterEach(() => {
jest.clearAllMocks();
});

it('Render modal updates Api Key', async () => {
renderWithProviders(
<UpdateApiKeyModalConfirmation
onCancel={onCancel}
idsToUpdate={['2']}
apiUpdateApiKeyCall={apiUpdateApiKeyCall}
setIsLoadingState={setIsLoadingState}
onUpdated={onUpdated}
onSearchPopulate={onSearchPopulate}
/>
);

expect(
await screen.findByText('You will not be able to recover the old API key')
).toBeInTheDocument();
});

it('Cancel modal updates Api Key', async () => {
renderWithProviders(
<UpdateApiKeyModalConfirmation
onCancel={onCancel}
idsToUpdate={['2']}
apiUpdateApiKeyCall={apiUpdateApiKeyCall}
setIsLoadingState={setIsLoadingState}
onUpdated={onUpdated}
onSearchPopulate={onSearchPopulate}
/>
);

fireEvent.click(await screen.findByText('Cancel'));
expect(onCancel).toHaveBeenCalled();
});

it('Update an Api Key', async () => {
apiUpdateApiKeyCall.mockResolvedValue({});
renderWithProviders(
<UpdateApiKeyModalConfirmation
onCancel={onCancel}
idsToUpdate={['2']}
apiUpdateApiKeyCall={apiUpdateApiKeyCall}
setIsLoadingState={setIsLoadingState}
onUpdated={onUpdated}
onSearchPopulate={onSearchPopulate}
/>
);

fireEvent.click(await screen.findByText('Update'));
expect(setIsLoadingState).toBeCalledTimes(1);
expect(apiUpdateApiKeyCall).toHaveBeenLastCalledWith(expect.objectContaining({ ids: ['2'] }));
await waitFor(() => {
expect(setIsLoadingState).toBeCalledTimes(2);
expect(onUpdated).toHaveBeenCalled();
});
});

it('Failed to update an Api Key', async () => {
apiUpdateApiKeyCall.mockRejectedValue(500);
renderWithProviders(
<UpdateApiKeyModalConfirmation
onCancel={onCancel}
idsToUpdate={['2']}
apiUpdateApiKeyCall={apiUpdateApiKeyCall}
setIsLoadingState={setIsLoadingState}
onUpdated={onUpdated}
onSearchPopulate={onSearchPopulate}
/>
);

fireEvent.click(await screen.findByText('Update'));
expect(setIsLoadingState).toBeCalledTimes(1);
expect(apiUpdateApiKeyCall).toHaveBeenLastCalledWith(expect.objectContaining({ ids: ['2'] }));
await waitFor(() => {
expect(setIsLoadingState).toBeCalledTimes(2);
expect(addError).toHaveBeenCalled();
expect(addError.mock.calls[0]).toMatchInlineSnapshot(`
Array [
500,
Object {
"title": "Failed to update the API key",
},
]
`);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,7 @@ const renderWithProviders = (ui: any) => {
return render(ui, { wrapper: AllTheProviders });
};

// FLAKY: https://github.com/elastic/kibana/issues/134922
// FLAKY: https://github.com/elastic/kibana/issues/134923

describe.skip('Update Api Key', () => {
describe('Update Api Key', () => {
const addSuccess = jest.fn();
const addError = jest.fn();

Expand Down Expand Up @@ -177,7 +174,7 @@ describe.skip('Update Api Key', () => {
cleanup();
});

it('Updates the Api Key successfully', async () => {
it('Have the option to update API key', async () => {
bulkUpdateAPIKey.mockResolvedValueOnce({ errors: [], total: 1, rules: [], skipped: [] });
renderWithProviders(<RulesList />);

Expand All @@ -186,45 +183,7 @@ describe.skip('Update Api Key', () => {
fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]);
expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument();

fireEvent.click(await screen.findByText('Update API key'));
expect(screen.getByText('You will not be able to recover the old API key')).toBeInTheDocument();

fireEvent.click(await screen.findByText('Cancel'));
expect(
screen.queryByText('You will not be able to recover the old API key')
).not.toBeInTheDocument();

fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]);
expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument();

fireEvent.click(await screen.findByText('Update API key'));

fireEvent.click(await screen.findByTestId('confirmModalConfirmButton'));
await waitFor(() => expect(addSuccess).toHaveBeenCalledWith('Updated API key for 1 rule.'));
expect(bulkUpdateAPIKey).toHaveBeenCalledWith(expect.objectContaining({ ids: ['2'] }));
expect(screen.queryByText("You can't recover the old API key")).not.toBeInTheDocument();
});

it('Update API key fails', async () => {
bulkUpdateAPIKey.mockRejectedValueOnce(500);
renderWithProviders(<RulesList />);

expect(await screen.findByText('test rule ok')).toBeInTheDocument();

fireEvent.click((await screen.findAllByTestId('selectActionButton'))[1]);
expect(screen.getByTestId('collapsedActionPanel')).toBeInTheDocument();

fireEvent.click(await screen.findByText('Update API key'));
expect(screen.getByText('You will not be able to recover the old API key')).toBeInTheDocument();

fireEvent.click(await screen.findByText('Update'));
await waitFor(() =>
expect(addError).toHaveBeenCalledWith(500, { title: 'Failed to update the API key' })
);
expect(bulkUpdateAPIKey).toHaveBeenCalledWith(expect.objectContaining({ ids: ['2'] }));
expect(
screen.queryByText('You will not be able to recover the old API key')
).not.toBeInTheDocument();
expect(screen.queryByText('Update API key')).toBeInTheDocument();
});
});

Expand Down

0 comments on commit d4dcc78

Please sign in to comment.