Skip to content

Commit

Permalink
[SecuritySolution] Retrieve onboarding steps (elastic#181442)
Browse files Browse the repository at this point in the history
## Summary

Fixes elastic#181190
Relevant PRs: elastic#143598 |
elastic#163739


### Steps to Verify: 
1. Entering
[these](https://p.elstc.co/paste/sEmk++Tb#mjwuX7IN8hIN+kOy5gdtfweQNi9sUl+4lVRAewc6hR+)
in your kibana.dev.yml
2. Execute this command to set the Guided onboarding steps to
alertsCases
```
curl --location --request PUT 'http://localhost:5601/internal/guided_onboarding/state' \
--header 'kbn-xsrf: cypress-creds' \
--header 'x-elastic-internal-origin: security-solution' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {YOUR_AUTH_TOKEN}' \.    // If you are using Postman just fill in the Auth tab
--data '{
  "status": "in_progress",
  "guide": {
    "isActive": true,
    "status": "in_progress",
    "steps": [
      { "id": "add_data", "status": "complete" },
      { "id": "rules", "status": "complete" },
      { "id": "alertsCases", "status": "active" }
    ],
    "guideId": "siem"
  }
}'
```
3. Make sure you have alerts available.
4. To test the old flyout with Guided onboarding tour, please go to
Stack Management > Advanced settings > Expandable flyout **OFF**

### It compatible with the new expandable flyout:
1. It shows `expandable flyout tour` when the guided onboarding tour is
**not enabled**.
3. The first two steps should be `hidden` when the `left` expandable is
opened.
5. Most of the guided onboarding tour steps should be hidden when `Add
to new case` flyout or `Add to existing case` modal opened.
6. Once the test case is created, the `insight section` and `correlation
tab` should be opened automatically to fetch cases.
7. `expandable flyout tour` should be visible again after the guided
onboarding tour is finished.






https://github.com/elastic/kibana/assets/6295984/b19bfce9-ec02-4291-b616-e24d3e984a03






### It compatible with the old flyout: 




https://github.com/elastic/kibana/assets/6295984/b10b8bdf-e159-4663-b455-1f4541358a11


### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Sergi Massaneda <sergi.massaneda@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people authored and kaanyalti committed May 28, 2024
1 parent 32d833e commit 6f9654c
Show file tree
Hide file tree
Showing 46 changed files with 1,101 additions and 383 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useCasesToast } from '../../../common/use_cases_toast';
import { alertComment } from '../../../containers/mock';
import { useCreateAttachments } from '../../../containers/use_create_attachments';
import { CasesContext } from '../../cases_context';
import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer';
import { CasesContextStoreActionsList } from '../../cases_context/state/cases_context_reducer';
import { ExternalReferenceAttachmentTypeRegistry } from '../../../client/attachment_framework/external_reference_registry';
import type { AddToExistingCaseModalProps } from './use_cases_add_to_existing_case_modal';
import { useCasesAddToExistingCaseModal } from './use_cases_add_to_existing_case_modal';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CaseStatuses } from '../../../../common/types/domain';
import type { AllCasesSelectorModalProps } from '.';
import { useCasesToast } from '../../../common/use_cases_toast';
import type { CaseUI } from '../../../containers/types';
import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer';
import { CasesContextStoreActionsList } from '../../cases_context/state/cases_context_reducer';
import { useCasesContext } from '../../cases_context/use_cases_context';
import { useCasesAddToNewCaseFlyout } from '../../create/flyout/use_cases_add_to_new_case_flyout';
import type { CaseAttachmentsWithoutOwner } from '../../../types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getAllCasesSelectorModalNoProviderLazy } from '../../client/ui/get_all_
import { getCreateCaseFlyoutLazyNoProvider } from '../../client/ui/get_create_case_flyout';
import type { AppMockRenderer } from '../../common/mock';
import { createAppMockRenderer } from '../../common/mock';
import { getInitialCasesContextState } from './cases_context_reducer';
import { getInitialCasesContextState } from './state/cases_context_reducer';
import { CasesGlobalComponents } from './cases_global_components';

jest.mock('../../client/ui/get_create_case_flyout');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React from 'react';
import { getAllCasesSelectorModalNoProviderLazy } from '../../client/ui/get_all_cases_selector_modal';
import { getCreateCaseFlyoutLazyNoProvider } from '../../client/ui/get_create_case_flyout';
import type { CasesContextState } from './cases_context_reducer';
import type { CasesContextState } from './state/cases_context_reducer';

export const CasesGlobalComponents = React.memo(({ state }: { state: CasesContextState }) => {
return (
Expand Down
23 changes: 13 additions & 10 deletions x-pack/plugins/cases/public/components/cases_context/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { FilesContext } from '@kbn/shared-ux-file-context';

import type { QueryClient } from '@tanstack/react-query';
import { QueryClientProvider } from '@tanstack/react-query';
import type { CasesContextStoreAction } from './cases_context_reducer';
import type {
CasesFeaturesAllRequired,
CasesFeatures,
Expand All @@ -29,7 +28,9 @@ import { CasesGlobalComponents } from './cases_global_components';
import { DEFAULT_FEATURES } from '../../../common/constants';
import { constructFileKindIdByOwner } from '../../../common/files';
import { DEFAULT_BASE_PATH } from '../../common/navigation';
import { casesContextReducer, getInitialCasesContextState } from './cases_context_reducer';
import type { CasesContextStoreAction } from './state/cases_context_reducer';
import { casesContextReducer, getInitialCasesContextState } from './state/cases_context_reducer';
import { CasesStateContext } from './state/cases_state_context';
import { isRegisteredOwner } from '../../files';
import { casesQueryClient } from './query_client';

Expand Down Expand Up @@ -152,14 +153,16 @@ export const CasesProvider: FC<

return (
<QueryClientProvider client={queryClient}>
<CasesContext.Provider value={value}>
{applyFilesContext(
<>
<CasesGlobalComponents state={state} />
{children}
</>
)}
</CasesContext.Provider>
<CasesStateContext.Provider value={state}>
<CasesContext.Provider value={value}>
{applyFilesContext(
<>
<CasesGlobalComponents state={state} />
{children}
</>
)}
</CasesContext.Provider>
</CasesStateContext.Provider>
</QueryClientProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
*/

import { assertNever } from '@kbn/std';
import type { AllCasesSelectorModalProps } from '../all_cases/selector_modal';
import type { CreateCaseFlyoutProps } from '../create/flyout';
import type { AllCasesSelectorModalProps } from '../../all_cases/selector_modal';
import type { CreateCaseFlyoutProps } from '../../create/flyout';

export const getInitialCasesContextState = (): CasesContextState => {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 React, { useContext } from 'react';
import type { CasesContextState } from './cases_context_reducer';

export const CasesStateContext = React.createContext<CasesContextState | undefined>(undefined);

export const useCasesStateContext = () => {
const casesStateContext = useContext(CasesStateContext);
if (!casesStateContext) {
throw new Error(
'useCasesStateContext must be used within a CasesProvider and have a defined value. See https://github.com/elastic/kibana/blob/main/x-pack/plugins/cases/README.md#cases-ui'
);
}
return casesStateContext;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 { act, renderHook } from '@testing-library/react-hooks';
import { useCasesAddToExistingCaseModal } from '../../all_cases/selector_modal/use_cases_add_to_existing_case_modal';
import { createAppMockRenderer } from '../../../common/mock';
import { useIsAddToCaseOpen } from './use_is_add_to_case_open';
import { useCasesToast } from '../../../common/use_cases_toast';
import { useCasesAddToNewCaseFlyout } from '../../create/flyout/use_cases_add_to_new_case_flyout';

jest.mock('../../../common/use_cases_toast');
const useCasesToastMock = useCasesToast as jest.Mock;
useCasesToastMock.mockReturnValue({
showInfoToast: jest.fn(),
});

const { AppWrapper } = createAppMockRenderer();

describe('use is add to existing case modal open hook', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should throw if called outside of a cases context', () => {
const { result } = renderHook(useIsAddToCaseOpen);
expect(result.error?.message).toContain(
'useCasesStateContext must be used within a CasesProvider and have a defined value'
);
});

it('should return false when the add to case modal and flyout are not open', async () => {
const { result } = renderHook(useIsAddToCaseOpen, { wrapper: AppWrapper });
expect(result.current).toEqual(false);
});

it('should return true when the add to existing case modal opens', async () => {
const { result, rerender } = renderHook(
() => {
return {
modal: useCasesAddToExistingCaseModal(),
isOpen: useIsAddToCaseOpen(),
};
},
{ wrapper: AppWrapper }
);

expect(result.current.isOpen).toEqual(false);
act(() => {
result.current.modal.open();
});
rerender();
expect(result.current.isOpen).toEqual(true);
});

it('should return true when the add to new case flyout opens', async () => {
const { result, rerender } = renderHook(
() => {
return {
flyout: useCasesAddToNewCaseFlyout(),
isOpen: useIsAddToCaseOpen(),
};
},
{ wrapper: AppWrapper }
);

expect(result.current.isOpen).toEqual(false);
act(() => {
result.current.flyout.open();
});
rerender();
expect(result.current.isOpen).toEqual(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { useCasesStateContext } from './cases_state_context';

export type UseIsAddToCaseOpen = () => boolean;

/**
* This hook is to check if the "add to case" is open, either the modal or the flyout
*/
export const useIsAddToCaseOpen: UseIsAddToCaseOpen = () => {
const { selectCaseModal, createCaseFlyout } = useCasesStateContext();
return selectCaseModal.isModalOpen || createCaseFlyout.isFlyoutOpen;
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { renderHook } from '@testing-library/react-hooks';
import type { FC, PropsWithChildren } from 'react';
import React from 'react';
import { CasesContext } from '../../cases_context';
import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer';
import { CasesContextStoreActionsList } from '../../cases_context/state/cases_context_reducer';
import { useCasesAddToNewCaseFlyout } from './use_cases_add_to_new_case_flyout';
import { allCasesPermissions } from '../../../common/mock';
import { ExternalReferenceAttachmentTypeRegistry } from '../../../client/attachment_framework/external_reference_registry';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useCallback, useMemo } from 'react';
import type { CaseAttachmentsWithoutOwner } from '../../../types';
import { useCasesToast } from '../../../common/use_cases_toast';
import type { CaseUI } from '../../../containers/types';
import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer';
import { CasesContextStoreActionsList } from '../../cases_context/state/cases_context_reducer';
import { useCasesContext } from '../../cases_context/use_cases_context';
import type { CreateCaseFlyoutProps } from './create_case_flyout';

Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const uiMock: jest.Mocked<CasesPublicStart['ui']> = {

export const openAddToExistingCaseModalMock = jest.fn();
export const openAddToNewCaseFlyoutMock = jest.fn();
export const isAddToCaseOpenMock = jest.fn();

const hooksMock: jest.Mocked<CasesPublicStart['hooks']> = {
useCasesAddToNewCaseFlyout: jest.fn().mockImplementation(() => ({
Expand All @@ -35,6 +36,7 @@ const hooksMock: jest.Mocked<CasesPublicStart['hooks']> = {
useCasesAddToExistingCaseModal: jest.fn().mockImplementation(() => ({
open: openAddToExistingCaseModalMock,
})),
useIsAddToCaseOpen: isAddToCaseOpenMock,
};

const helpersMock: jest.Mocked<CasesPublicStart['helpers']> = {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/cases/public/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ describe('Cases Ui Plugin', () => {
hooks: {
useCasesAddToExistingCaseModal: expect.any(Function),
useCasesAddToNewCaseFlyout: expect.any(Function),
useIsAddToCaseOpen: expect.any(Function),
},
ui: {
getAllCasesSelectorModal: expect.any(Function),
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { APP_ID, APP_PATH } from '../common/constants';
import { APP_TITLE, APP_DESC } from './common/translations';
import { useCasesAddToExistingCaseModal } from './components/all_cases/selector_modal/use_cases_add_to_existing_case_modal';
import { useCasesAddToNewCaseFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout';
import { useIsAddToCaseOpen } from './components/cases_context/state/use_is_add_to_case_open';
import { createClientAPI } from './client/api';
import { canUseCases } from './client/helpers/can_use_cases';
import { getRuleIdFromEvent } from './client/helpers/get_rule_id_from_event';
Expand Down Expand Up @@ -190,6 +191,7 @@ export class CasesUiPlugin
hooks: {
useCasesAddToNewCaseFlyout,
useCasesAddToExistingCaseModal,
useIsAddToCaseOpen,
},
helpers: {
canUseCases: canUseCases(core.application.capabilities),
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/cases/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverle

import type { UseCasesAddToExistingCaseModal } from './components/all_cases/selector_modal/use_cases_add_to_existing_case_modal';
import type { UseCasesAddToNewCaseFlyout } from './components/create/flyout/use_cases_add_to_new_case_flyout';
import type { UseIsAddToCaseOpen } from './components/cases_context/state/use_is_add_to_case_open';
import type { canUseCases } from './client/helpers/can_use_cases';
import type { getRuleIdFromEvent } from './client/helpers/get_rule_id_from_event';
import type { GetCasesContextProps } from './client/ui/get_cases_context';
Expand Down Expand Up @@ -154,6 +155,7 @@ export interface CasesPublicStart {
hooks: {
useCasesAddToNewCaseFlyout: UseCasesAddToNewCaseFlyout;
useCasesAddToExistingCaseModal: UseCasesAddToExistingCaseModal;
useIsAddToCaseOpen: UseIsAddToCaseOpen;
};
helpers: {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jest.mock('../../../utils/route/use_route_spy');
jest.mock('@kbn/expandable-flyout', () => {
return {
useExpandableFlyoutApi: () => ({ openFlyout: mockOpenFlyout }),
useExpandableFlyoutState: () => ({ left: false }),
};
});

Expand All @@ -55,6 +56,7 @@ jest.mock('@kbn/kibana-react-plugin/public', () => {
...original,
};
});
jest.mock('../../guided_onboarding_tour/tour_step');

const mockRouteSpy: RouteSpyState = {
pageName: SecurityPageName.overview,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/sea
import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline';
import { TimelineId } from '../../../../../common/types';
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
import { useTourContext } from '../../guided_onboarding_tour';
import { AlertsCasesTourSteps, SecurityStepId } from '../../guided_onboarding_tour/tour_config';

type Props = EuiDataGridCellValueElementProps & {
columnHeaders: ColumnHeaderOptions[];
Expand Down Expand Up @@ -83,6 +85,11 @@ const RowActionComponent = ({
const isExpandableFlyoutInCreateRuleEnabled = useIsExperimentalFeatureEnabled(
'expandableFlyoutInCreateRuleEnabled'
);
const { activeStep, isTourShown } = useTourContext();
const shouldFocusOnOverviewTab =
(activeStep === AlertsCasesTourSteps.expandEvent ||
activeStep === AlertsCasesTourSteps.reviewAlertDetailsFlyout) &&
isTourShown(SecurityStepId.alertsCases);

const columnValues = useMemo(
() =>
Expand Down Expand Up @@ -123,6 +130,7 @@ const RowActionComponent = ({
openFlyout({
right: {
id: DocumentDetailsRightPanelKey,
path: shouldFocusOnOverviewTab ? { tab: 'overview' } : undefined,
params: {
id: eventId,
indexName,
Expand Down Expand Up @@ -156,7 +164,17 @@ const RowActionComponent = ({
})
);
}
}, [dispatch, eventId, indexName, openFlyout, tabType, tableId, showExpandableFlyout, telemetry]);
}, [
eventId,
indexName,
showExpandableFlyout,
tableId,
openFlyout,
shouldFocusOnOverviewTab,
telemetry,
dispatch,
tabType,
]);

const Action = controlColumn.rowCellRender;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallbac
};
});

jest.mock('../guided_onboarding_tour/tour_step', () => ({
GuidedOnboardingTourStep: jest.fn(({ children }) => (
<div data-test-subj="guided-onboarding">{children}</div>
)),
}));

jest.mock('../link_to');
describe('EventDetails', () => {
const defaultProps = {
Expand Down Expand Up @@ -168,6 +174,10 @@ describe('EventDetails', () => {
EVENT_DETAILS_CONTEXT_ID
);
});

test('renders GuidedOnboardingTourStep', () => {
expect(alertsWrapper.find('[data-test-subj="guided-onboarding"]').exists()).toEqual(true);
});
});

describe('threat intel tab', () => {
Expand Down
Loading

0 comments on commit 6f9654c

Please sign in to comment.