-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[8.x] [Security Solution] Repurpose attack discover tour into knowled…
…ge base tour (#196615) (#197536) # Backport This will backport the following commits from `main` to `8.x`: - [[Security Solution] Repurpose attack discover tour into knowledge base tour (#196615)](#196615) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Steph Milovic","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-23T21:02:35Z","message":"[Security Solution] Repurpose attack discover tour into knowledge base tour (#196615)","sha":"fa9bb19f14648bbe34493481df0b32838d0e5734","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team: SecuritySolution","backport:prev-minor","Team:Security Generative AI","v8.16.0"],"title":"[Security Solution] Repurpose attack discover tour into knowledge base tour","number":196615,"url":"https://github.com/elastic/kibana/pull/196615","mergeCommit":{"message":"[Security Solution] Repurpose attack discover tour into knowledge base tour (#196615)","sha":"fa9bb19f14648bbe34493481df0b32838d0e5734"}},"sourceBranch":"main","suggestedTargetBranches":["8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196615","number":196615,"mergeCommit":{"message":"[Security Solution] Repurpose attack discover tour into knowledge base tour (#196615)","sha":"fa9bb19f14648bbe34493481df0b32838d0e5734"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Steph Milovic <[email protected]>
- Loading branch information
1 parent
9a0500d
commit 7068760
Showing
24 changed files
with
388 additions
and
405 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export const NEW_FEATURES_TOUR_STORAGE_KEYS = { | ||
KNOWLEDGE_BASE: 'elasticAssistant.knowledgeBase.newFeaturesTour.v8.16', | ||
}; |
142 changes: 142 additions & 0 deletions
142
x-pack/packages/kbn-elastic-assistant/impl/tour/knowledge_base/index.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* 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 from 'react'; | ||
|
||
import { render, screen } from '@testing-library/react'; | ||
import { EuiTourStepProps } from '@elastic/eui'; | ||
import useLocalStorage from 'react-use/lib/useLocalStorage'; | ||
import { KnowledgeBaseTour } from '.'; | ||
import { TestProviders } from '../../mock/test_providers/test_providers'; | ||
import { useAssistantContext } from '../../..'; | ||
jest.mock('../../..'); | ||
jest.mock('react-use/lib/useLocalStorage'); | ||
jest.mock('@elastic/eui', () => { | ||
const original = jest.requireActual('@elastic/eui'); | ||
return { | ||
...original, | ||
EuiTourStep: ({ children, panelProps }: EuiTourStepProps) => | ||
children ? ( | ||
<div data-test-subj={panelProps?.['data-test-subj']}>{children}</div> | ||
) : ( | ||
<div data-test-subj={panelProps?.['data-test-subj']} /> | ||
), | ||
}; | ||
}); | ||
|
||
describe('Attack discovery tour', () => { | ||
const persistToLocalStorage = jest.fn(); | ||
const navigateToApp = jest.fn(); | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
(useAssistantContext as jest.Mock).mockReturnValue({ | ||
navigateToApp, | ||
assistantFeatures: { | ||
assistantKnowledgeBaseByDefault: true, | ||
}, | ||
}); | ||
jest.mocked(useLocalStorage).mockReturnValue([ | ||
{ | ||
currentTourStep: 1, | ||
isTourActive: true, | ||
}, | ||
persistToLocalStorage, | ||
] as unknown as ReturnType<typeof useLocalStorage>); | ||
}); | ||
|
||
it('should not render any tour steps when tour is not activated', () => { | ||
jest.mocked(useLocalStorage).mockReturnValue([ | ||
{ | ||
currentTourStep: 1, | ||
isTourActive: false, | ||
}, | ||
persistToLocalStorage, | ||
] as unknown as ReturnType<typeof useLocalStorage>); | ||
render( | ||
<KnowledgeBaseTour> | ||
<h1>{'Hello world'}</h1> | ||
</KnowledgeBaseTour>, | ||
{ | ||
wrapper: TestProviders, | ||
} | ||
); | ||
expect(screen.queryByTestId('knowledgeBase-tour-step-1')).toBeNull(); | ||
expect(screen.queryByTestId('knowledgeBase-tour-step-2')).toBeNull(); | ||
}); | ||
|
||
it('should not render any tour steps when knowledge base feature flag is not activated', () => { | ||
(useAssistantContext as jest.Mock).mockReturnValue({ | ||
navigateToApp, | ||
assistantFeatures: { | ||
assistantKnowledgeBaseByDefault: false, | ||
}, | ||
}); | ||
render( | ||
<KnowledgeBaseTour> | ||
<h1>{'Hello world'}</h1> | ||
</KnowledgeBaseTour>, | ||
{ | ||
wrapper: TestProviders, | ||
} | ||
); | ||
expect(screen.queryByTestId('knowledgeBase-tour-step-1')).toBeNull(); | ||
expect(screen.queryByTestId('knowledgeBase-tour-step-2')).toBeNull(); | ||
}); | ||
|
||
it('should not render any tour steps when tour is on step 2 and page is not knowledge base', () => { | ||
jest.mocked(useLocalStorage).mockReturnValue([ | ||
{ | ||
currentTourStep: 2, | ||
isTourActive: true, | ||
}, | ||
persistToLocalStorage, | ||
] as unknown as ReturnType<typeof useLocalStorage>); | ||
render( | ||
<KnowledgeBaseTour> | ||
<h1>{'Hello world'}</h1> | ||
</KnowledgeBaseTour>, | ||
{ | ||
wrapper: TestProviders, | ||
} | ||
); | ||
expect(screen.queryByTestId('knowledgeBase-tour-step-1')).toBeNull(); | ||
}); | ||
|
||
it('should render tour step 1 when element is mounted', async () => { | ||
const { getByTestId } = render( | ||
<KnowledgeBaseTour> | ||
<h1>{'Hello world'}</h1> | ||
</KnowledgeBaseTour>, | ||
{ | ||
wrapper: TestProviders, | ||
} | ||
); | ||
|
||
expect(getByTestId('knowledgeBase-tour-step-1')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render tour video when tour is on step 2 and page is knowledge base', () => { | ||
jest.mocked(useLocalStorage).mockReturnValue([ | ||
{ | ||
currentTourStep: 2, | ||
isTourActive: true, | ||
}, | ||
persistToLocalStorage, | ||
] as unknown as ReturnType<typeof useLocalStorage>); | ||
const { getByTestId } = render(<KnowledgeBaseTour isKbSettingsPage />, { | ||
wrapper: TestProviders, | ||
}); | ||
expect(screen.queryByTestId('knowledgeBase-tour-step-1')).toBeNull(); | ||
expect(getByTestId('knowledgeBase-tour-step-2')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should advance to tour step 2 when page is knowledge base', () => { | ||
render(<KnowledgeBaseTour isKbSettingsPage />, { wrapper: TestProviders }); | ||
const nextStep = persistToLocalStorage.mock.calls[0][0]; | ||
expect(nextStep()).toEqual({ isTourActive: true, currentTourStep: 2 }); | ||
}); | ||
}); |
137 changes: 137 additions & 0 deletions
137
x-pack/packages/kbn-elastic-assistant/impl/tour/knowledge_base/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
/* | ||
* The knowledge base tour for 8.14 | ||
* | ||
* */ | ||
|
||
import React, { useCallback, useEffect, useMemo, useState } from 'react'; | ||
import { EuiButton, EuiButtonEmpty, EuiTourStep, EuiTourStepProps } from '@elastic/eui'; | ||
import useLocalStorage from 'react-use/lib/useLocalStorage'; | ||
import { KNOWLEDGE_BASE_TAB } from '../../assistant/settings/const'; | ||
import { useAssistantContext } from '../../..'; | ||
import { VideoToast } from './video_toast'; | ||
import { NEW_FEATURES_TOUR_STORAGE_KEYS } from '../const'; | ||
import { knowledgeBaseTourStepOne, tourConfig } from './step_config'; | ||
import * as i18n from './translations'; | ||
|
||
interface TourState { | ||
currentTourStep: number; | ||
isTourActive: boolean; | ||
} | ||
const KnowledgeBaseTourComp: React.FC<{ | ||
children?: EuiTourStepProps['children']; | ||
isKbSettingsPage?: boolean; | ||
}> = ({ children, isKbSettingsPage = false }) => { | ||
const { | ||
navigateToApp, | ||
assistantFeatures: { assistantKnowledgeBaseByDefault: enableKnowledgeBaseByDefault }, | ||
} = useAssistantContext(); | ||
|
||
const [tourState, setTourState] = useLocalStorage<TourState>( | ||
NEW_FEATURES_TOUR_STORAGE_KEYS.KNOWLEDGE_BASE, | ||
tourConfig | ||
); | ||
|
||
const advanceToVideoStep = useCallback( | ||
() => | ||
setTourState((prev = tourConfig) => ({ | ||
...prev, | ||
currentTourStep: 2, | ||
})), | ||
[setTourState] | ||
); | ||
|
||
useEffect(() => { | ||
if (tourState?.isTourActive && isKbSettingsPage) { | ||
advanceToVideoStep(); | ||
} | ||
}, [advanceToVideoStep, isKbSettingsPage, tourState?.isTourActive]); | ||
|
||
const finishTour = useCallback( | ||
() => | ||
setTourState((prev = tourConfig) => ({ | ||
...prev, | ||
isTourActive: false, | ||
})), | ||
[setTourState] | ||
); | ||
|
||
const navigateToKnowledgeBase = useCallback( | ||
() => | ||
navigateToApp('management', { | ||
path: `kibana/securityAiAssistantManagement?tab=${KNOWLEDGE_BASE_TAB}`, | ||
}), | ||
[navigateToApp] | ||
); | ||
|
||
const nextStep = useCallback(() => { | ||
if (tourState?.currentTourStep === 1) { | ||
navigateToKnowledgeBase(); | ||
advanceToVideoStep(); | ||
} | ||
}, [tourState?.currentTourStep, navigateToKnowledgeBase, advanceToVideoStep]); | ||
|
||
const footerAction = useMemo( | ||
() => [ | ||
// if exit, set tour to the video step without navigating to the page | ||
<EuiButtonEmpty size="s" color="text" onClick={advanceToVideoStep}> | ||
{i18n.KNOWLEDGE_BASE_TOUR_EXIT} | ||
</EuiButtonEmpty>, | ||
// if next, set tour to the video step and navigate to the page | ||
<EuiButton color="success" size="s" onClick={nextStep}> | ||
{i18n.KNOWLEDGE_BASE_TRY_IT} | ||
</EuiButton>, | ||
], | ||
[advanceToVideoStep, nextStep] | ||
); | ||
|
||
const isTestAutomation = | ||
// @ts-ignore | ||
window.Cypress != null || // TODO: temporary workaround to disable the tour when running in Cypress, because the tour breaks other projects Cypress tests | ||
navigator.webdriver === true; // TODO: temporary workaround to disable the tour when running in the FTR, because the tour breaks other projects FTR tests | ||
|
||
const [isTimerExhausted, setIsTimerExhausted] = useState(false); | ||
|
||
useEffect(() => { | ||
const timer = setTimeout(() => { | ||
setIsTimerExhausted(true); | ||
}, 1000); | ||
|
||
return () => clearTimeout(timer); | ||
}, []); | ||
|
||
if (!enableKnowledgeBaseByDefault || isTestAutomation || !tourState?.isTourActive) { | ||
return children ?? null; | ||
} | ||
|
||
return tourState?.currentTourStep === 1 && children ? ( | ||
<EuiTourStep | ||
anchorPosition={'downRight'} | ||
content={knowledgeBaseTourStepOne.content} | ||
footerAction={footerAction} | ||
isStepOpen={isTimerExhausted} | ||
maxWidth={450} | ||
onFinish={advanceToVideoStep} | ||
panelProps={{ | ||
'data-test-subj': `knowledgeBase-tour-step-1`, | ||
}} | ||
step={1} | ||
stepsTotal={1} | ||
title={knowledgeBaseTourStepOne.title} | ||
> | ||
{children} | ||
</EuiTourStep> | ||
) : isKbSettingsPage ? ( | ||
<VideoToast onClose={finishTour} /> | ||
) : ( | ||
children ?? null | ||
); | ||
}; | ||
|
||
export const KnowledgeBaseTour = React.memo(KnowledgeBaseTourComp); |
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.