mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[8.16] [Security Solution] Repurpose attack discover tour into knowledge base tour (#196615) (#197535)
# Backport This will backport the following commits from `main` to `8.16`: - [[Security Solution] Repurpose attack discover tour into knowledge base tour (#196615)](https://github.com/elastic/kibana/pull/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":"stephanie.milovic@elastic.co"},"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 <stephanie.milovic@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
871137b456
commit
a8d1c24dab
24 changed files with 388 additions and 405 deletions
|
@ -47,7 +47,7 @@ export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
|
|||
TIMELINES: 'securitySolution.security.timelineFlyoutHeader.saveTimelineTour',
|
||||
TIMELINE: 'securitySolution.timeline.newFeaturesTour.v8.12',
|
||||
FLYOUT: 'securitySolution.documentDetails.newFeaturesTour.v8.14',
|
||||
ATTACK_DISCOVERY: 'securitySolution.attackDiscovery.newFeaturesTour.v8.14',
|
||||
KNOWLEDGE_BASE: 'elasticAssistant.knowledgeBase.newFeaturesTour.v8.16',
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -424,7 +424,6 @@ export const NEW_FEATURES_TOUR_STORAGE_KEYS = {
|
|||
TIMELINES: 'securitySolution.security.timelineFlyoutHeader.saveTimelineTour',
|
||||
TIMELINE: 'securitySolution.timeline.newFeaturesTour.v8.12',
|
||||
FLYOUT: 'securitySolution.documentDetails.newFeaturesTour.v8.14',
|
||||
ATTACK_DISCOVERY: 'securitySolution.attackDiscovery.newFeaturesTour.v8.14',
|
||||
};
|
||||
|
||||
export const RULE_DETAILS_EXECUTION_LOG_TABLE_SHOW_METRIC_COLUMNS_STORAGE_KEY =
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { type ReactNode, useMemo, useState, useCallback } from 'react';
|
||||
import React, { type ReactNode, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiThemeProvider, useEuiTheme, type EuiThemeComputed } from '@elastic/eui';
|
||||
import { IS_DRAGGING_CLASS_NAME } from '@kbn/securitysolution-t-grid';
|
||||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
|
||||
import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout';
|
||||
import { AttackDiscoveryTour } from '../../../attack_discovery/tour';
|
||||
import { URL_PARAM_KEY } from '../../../common/hooks/use_url_state';
|
||||
import { SecuritySolutionFlyout, TimelineFlyout } from '../../../flyout';
|
||||
import { useSecuritySolutionNavigation } from '../../../common/components/navigation/use_security_solution_navigation';
|
||||
|
@ -56,11 +55,7 @@ export type SecuritySolutionTemplateWrapperProps = Omit<KibanaPageTemplateProps,
|
|||
|
||||
export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionTemplateWrapperProps> =
|
||||
React.memo(({ children, ...rest }) => {
|
||||
const [didMount, setDidMount] = useState(false);
|
||||
const onMount = useCallback(() => {
|
||||
setDidMount(true);
|
||||
}, []);
|
||||
const solutionNavProps = useSecuritySolutionNavigation(onMount);
|
||||
const solutionNavProps = useSecuritySolutionNavigation();
|
||||
const [isTimelineBottomBarVisible] = useShowTimeline();
|
||||
const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []);
|
||||
const { show: isShowingTimelineOverlay } = useDeepEqualSelector((state) =>
|
||||
|
@ -107,8 +102,6 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionTemplateW
|
|||
{children}
|
||||
<SecuritySolutionFlyout />
|
||||
</ExpandableFlyoutProvider>
|
||||
|
||||
{didMount && <AttackDiscoveryTour />}
|
||||
</KibanaPageTemplate.Section>
|
||||
{isTimelineBottomBarVisible && (
|
||||
<KibanaPageTemplate.BottomBar data-test-subj="timeline-bottom-bar-container">
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* 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 { useIsElementMounted } from '../../detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/use_is_element_mounted';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import {
|
||||
createMockStore,
|
||||
createSecuritySolutionStorageMock,
|
||||
TestProviders,
|
||||
} from '../../common/mock';
|
||||
import { useKibana as mockUseKibana } from '../../common/lib/kibana/__mocks__';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { AttackDiscoveryTour } from '.';
|
||||
import { ATTACK_DISCOVERY_TOUR_CONFIG_ANCHORS } from './step_config';
|
||||
import { NEW_FEATURES_TOUR_STORAGE_KEYS, SecurityPageName } from '../../../common/constants';
|
||||
import type { RouteSpyState } from '../../common/utils/route/types';
|
||||
import { useRouteSpy } from '../../common/utils/route/use_route_spy';
|
||||
|
||||
const mockRouteSpy: RouteSpyState = {
|
||||
pageName: SecurityPageName.overview,
|
||||
detailName: undefined,
|
||||
tabName: undefined,
|
||||
search: '',
|
||||
pathName: '/',
|
||||
};
|
||||
jest.mock(
|
||||
'../../detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/use_is_element_mounted'
|
||||
);
|
||||
jest.mock('../../common/lib/kibana');
|
||||
jest.mock('../../common/utils/route/use_route_spy');
|
||||
jest.mock('@elastic/eui', () => {
|
||||
const original = jest.requireActual('@elastic/eui');
|
||||
return {
|
||||
...original,
|
||||
EuiTourStep: () => <div data-test-subj="attackDiscovery-tour-step-1" />,
|
||||
};
|
||||
});
|
||||
const mockedUseKibana = mockUseKibana();
|
||||
|
||||
const { storage: storageMock } = createSecuritySolutionStorageMock();
|
||||
const mockStore = createMockStore(undefined, undefined, undefined, storageMock);
|
||||
|
||||
const TestComponent = () => {
|
||||
return (
|
||||
<TestProviders store={mockStore}>
|
||||
<div id={ATTACK_DISCOVERY_TOUR_CONFIG_ANCHORS.NAV_LINK} />
|
||||
<AttackDiscoveryTour />
|
||||
</TestProviders>
|
||||
);
|
||||
};
|
||||
|
||||
describe('Attack discovery tour', () => {
|
||||
beforeAll(() => {
|
||||
(useIsElementMounted as jest.Mock).mockReturnValue(true);
|
||||
(useRouteSpy as jest.Mock).mockReturnValue([mockRouteSpy]);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
...mockedUseKibana,
|
||||
services: {
|
||||
...mockedUseKibana.services,
|
||||
storage: storageMock,
|
||||
},
|
||||
});
|
||||
|
||||
storageMock.clear();
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not render tour step 1 when element is not mounted', () => {
|
||||
(useIsElementMounted as jest.Mock).mockReturnValueOnce(false);
|
||||
render(<TestComponent />);
|
||||
expect(screen.queryByTestId('attackDiscovery-tour-step-1')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render any tour steps when tour is not activated', () => {
|
||||
storageMock.set(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY, {
|
||||
currentTourStep: 1,
|
||||
isTourActive: false,
|
||||
});
|
||||
render(<TestComponent />);
|
||||
expect(screen.queryByTestId('attackDiscovery-tour-step-1')).toBeNull();
|
||||
expect(screen.queryByTestId('attackDiscovery-tour-step-2')).toBeNull();
|
||||
});
|
||||
|
||||
it('should not render any tour steps when tour is on step 2 and page is not attack discovery', () => {
|
||||
storageMock.set(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY, {
|
||||
currentTourStep: 2,
|
||||
isTourActive: true,
|
||||
});
|
||||
const { debug } = render(<TestComponent />);
|
||||
expect(screen.queryByTestId('attackDiscovery-tour-step-1')).toBeNull();
|
||||
debug();
|
||||
});
|
||||
|
||||
it('should render tour step 1 when element is mounted', async () => {
|
||||
const { getByTestId } = render(<TestComponent />);
|
||||
|
||||
expect(getByTestId('attackDiscovery-tour-step-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render tour video when tour is on step 2 and page is attack discovery', () => {
|
||||
(useRouteSpy as jest.Mock).mockReturnValue([
|
||||
{ ...mockRouteSpy, pageName: SecurityPageName.attackDiscovery },
|
||||
]);
|
||||
storageMock.set(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY, {
|
||||
currentTourStep: 2,
|
||||
isTourActive: true,
|
||||
});
|
||||
const { getByTestId } = render(<TestComponent />);
|
||||
expect(screen.queryByTestId('attackDiscovery-tour-step-1')).toBeNull();
|
||||
expect(getByTestId('attackDiscovery-tour-step-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should advance to tour step 2 when page is attack discovery', () => {
|
||||
(useRouteSpy as jest.Mock).mockReturnValue([
|
||||
{ ...mockRouteSpy, pageName: SecurityPageName.attackDiscovery },
|
||||
]);
|
||||
storageMock.set(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY, {
|
||||
currentTourStep: 1,
|
||||
isTourActive: true,
|
||||
});
|
||||
render(<TestComponent />);
|
||||
expect(
|
||||
storageMock.get(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY).currentTourStep
|
||||
).toEqual(2);
|
||||
});
|
||||
});
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* 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 attack discovery tour for 8.14
|
||||
*
|
||||
* */
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { EuiButton, EuiButtonEmpty, EuiTourStep } from '@elastic/eui';
|
||||
import { useRouteSpy } from '../../common/utils/route/use_route_spy';
|
||||
import { VideoToast } from './video_toast';
|
||||
import { useIsElementMounted } from '../../detection_engine/rule_management_ui/components/rules_table/rules_table/guided_onboarding/use_is_element_mounted';
|
||||
import { NEW_FEATURES_TOUR_STORAGE_KEYS, SecurityPageName } from '../../../common/constants';
|
||||
import { useKibana, useNavigation } from '../../common/lib/kibana';
|
||||
import { attackDiscoveryTourStepOne, tourConfig } from './step_config';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface TourState {
|
||||
currentTourStep: number;
|
||||
isTourActive: boolean;
|
||||
}
|
||||
|
||||
const AttackDiscoveryTourComp = () => {
|
||||
const {
|
||||
services: { storage },
|
||||
} = useKibana();
|
||||
|
||||
const { navigateTo } = useNavigation();
|
||||
const [{ pageName }] = useRouteSpy();
|
||||
const [tourState, setTourState] = useState<TourState>(
|
||||
storage.get(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY) ?? tourConfig
|
||||
);
|
||||
|
||||
const advanceToVideoStep = useCallback(() => {
|
||||
setTourState((prev) => {
|
||||
storage.set(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY, {
|
||||
...prev,
|
||||
currentTourStep: 2,
|
||||
});
|
||||
return {
|
||||
...prev,
|
||||
currentTourStep: 2,
|
||||
};
|
||||
});
|
||||
}, [storage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tourState.isTourActive && pageName === SecurityPageName.attackDiscovery) {
|
||||
advanceToVideoStep();
|
||||
}
|
||||
}, [advanceToVideoStep, pageName, tourState.isTourActive]);
|
||||
|
||||
const finishTour = useCallback(() => {
|
||||
setTourState((prev) => {
|
||||
storage.set(NEW_FEATURES_TOUR_STORAGE_KEYS.ATTACK_DISCOVERY, {
|
||||
...prev,
|
||||
isTourActive: false,
|
||||
});
|
||||
return {
|
||||
...prev,
|
||||
isTourActive: false,
|
||||
};
|
||||
});
|
||||
}, [storage]);
|
||||
|
||||
const navigateToAttackDiscovery = useCallback(() => {
|
||||
navigateTo({
|
||||
deepLinkId: SecurityPageName.attackDiscovery,
|
||||
});
|
||||
}, [navigateTo]);
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
if (tourState.currentTourStep === 1) {
|
||||
navigateToAttackDiscovery();
|
||||
advanceToVideoStep();
|
||||
}
|
||||
}, [tourState.currentTourStep, navigateToAttackDiscovery, 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.ATTACK_DISCOVERY_TOUR_EXIT}
|
||||
</EuiButtonEmpty>,
|
||||
// if next, set tour to the video step and navigate to the page
|
||||
<EuiButton color="success" size="s" onClick={nextStep}>
|
||||
{i18n.ATTACK_DISCOVERY_TRY_IT}
|
||||
</EuiButton>,
|
||||
],
|
||||
[advanceToVideoStep, nextStep]
|
||||
);
|
||||
|
||||
const isElementAtCurrentStepMounted = useIsElementMounted(attackDiscoveryTourStepOne?.anchor);
|
||||
|
||||
const isTestAutomation =
|
||||
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
|
||||
|
||||
if (
|
||||
isTestAutomation ||
|
||||
!tourState.isTourActive ||
|
||||
(tourState.currentTourStep === 1 && !isElementAtCurrentStepMounted)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return tourState.currentTourStep === 1 ? (
|
||||
<EuiTourStep
|
||||
anchor={`#${attackDiscoveryTourStepOne.anchor}`}
|
||||
content={attackDiscoveryTourStepOne.content}
|
||||
footerAction={footerAction}
|
||||
isStepOpen
|
||||
maxWidth={450}
|
||||
onFinish={advanceToVideoStep}
|
||||
panelProps={{
|
||||
'data-test-subj': `attackDiscovery-tour-step-1`,
|
||||
}}
|
||||
repositionOnScroll
|
||||
step={1}
|
||||
stepsTotal={1}
|
||||
title={attackDiscoveryTourStepOne.title}
|
||||
/>
|
||||
) : pageName === SecurityPageName.attackDiscovery ? (
|
||||
<VideoToast onClose={finishTour} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const AttackDiscoveryTour = React.memo(AttackDiscoveryTourComp);
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB |
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* 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 i18n from './translations';
|
||||
|
||||
export const ATTACK_DISCOVERY_TOUR_CONFIG_ANCHORS = {
|
||||
NAV_LINK: 'solutionSideNavItemLink-attack_discovery',
|
||||
};
|
||||
|
||||
export const attackDiscoveryTourStepOne = {
|
||||
title: i18n.ATTACK_DISCOVERY_TOUR_ATTACK_DISCOVERY_TITLE,
|
||||
content: i18n.ATTACK_DISCOVERY_TOUR_ATTACK_DISCOVERY_DESC,
|
||||
anchor: ATTACK_DISCOVERY_TOUR_CONFIG_ANCHORS.NAV_LINK,
|
||||
};
|
||||
|
||||
export const tourConfig = {
|
||||
currentTourStep: 1,
|
||||
isTourActive: true,
|
||||
};
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ATTACK_DISCOVERY_TOUR_ATTACK_DISCOVERY_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.navStep.title',
|
||||
{
|
||||
defaultMessage: 'Introducing attack discovery',
|
||||
}
|
||||
);
|
||||
|
||||
export const ATTACK_DISCOVERY_TOUR_ATTACK_DISCOVERY_DESC = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.navStep.desc',
|
||||
{
|
||||
defaultMessage:
|
||||
'Leverage Generative AI to find relationships among your alerts and describe attack chains.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ATTACK_DISCOVERY_TOUR_VIDEO_STEP_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.videoStep.title',
|
||||
{
|
||||
defaultMessage: 'Start discovering attacks',
|
||||
}
|
||||
);
|
||||
|
||||
export const ATTACK_DISCOVERY_TOUR_VIDEO_STEP_DESC = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.videoStep.desc',
|
||||
{
|
||||
defaultMessage:
|
||||
'Dive into data-driven attack discoveries and streamline your workflow with our intuitive AI technology, designed to elevate your productivity instantly.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ATTACK_DISCOVERY_TOUR_EXIT = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.exit',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
);
|
||||
|
||||
export const ATTACK_DISCOVERY_TRY_IT = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.tryIt',
|
||||
{
|
||||
defaultMessage: 'Try it',
|
||||
}
|
||||
);
|
||||
|
||||
export const WATCH_OVERVIEW_VIDEO = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.tour.video',
|
||||
{
|
||||
defaultMessage: 'Watch overview video',
|
||||
}
|
||||
);
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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 userEvent from '@testing-library/user-event';
|
||||
import { VideoToast } from './video_toast';
|
||||
|
||||
describe('VideoToast', () => {
|
||||
const onCloseMock = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window, 'open').mockImplementation(() => null);
|
||||
render(<VideoToast onClose={onCloseMock} />);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
it('should render the video toast', () => {
|
||||
const videoToast = screen.getByTestId('attackDiscovery-tour-step-2');
|
||||
expect(videoToast).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the video gif', () => {
|
||||
const videoGif = screen.getByTestId('video-gif');
|
||||
expect(videoGif).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open the video in a new tab when the gif is clicked', async () => {
|
||||
const videoGif = screen.getByTestId('video-gif');
|
||||
await userEvent.click(videoGif);
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://videos.elastic.co/watch/BrDaDBAAvdygvemFKNAkBW',
|
||||
'_blank'
|
||||
);
|
||||
});
|
||||
|
||||
it('should open the video in a new tab when the "Watch overview video" button is clicked', async () => {
|
||||
const watchVideoButton = screen.getByRole('button', { name: 'Watch overview video' });
|
||||
await userEvent.click(watchVideoButton);
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://videos.elastic.co/watch/BrDaDBAAvdygvemFKNAkBW',
|
||||
'_blank'
|
||||
);
|
||||
});
|
||||
|
||||
it('should call the onClose callback when the close button is clicked', async () => {
|
||||
const closeButton = screen.getByTestId('toastCloseButton');
|
||||
await userEvent.click(closeButton);
|
||||
expect(onCloseMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiButton,
|
||||
EuiIcon,
|
||||
EuiImage,
|
||||
EuiToast,
|
||||
EuiPortal,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import * as i18n from './translations';
|
||||
import theGif from './overview.gif';
|
||||
|
||||
const VIDEO_CONTENT_WIDTH = 250;
|
||||
const VIDEO_PAGE = `https://videos.elastic.co/watch/BrDaDBAAvdygvemFKNAkBW`;
|
||||
|
||||
const VideoComponent: React.FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
const openVideoInNewTab = useCallback(() => {
|
||||
window.open(VIDEO_PAGE, '_blank');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiPortal>
|
||||
<div
|
||||
data-test-subj="attackDiscovery-tour-step-2"
|
||||
css={{ position: 'fixed', bottom: 16, right: 16, zIndex: 9999 }}
|
||||
>
|
||||
<EuiToast onClose={onClose} css={{ maxWidth: VIDEO_CONTENT_WIDTH }}>
|
||||
<EuiImage
|
||||
onClick={openVideoInNewTab}
|
||||
css={{ marginTop: 20, '&:hover': { cursor: 'pointer' } }}
|
||||
src={theGif}
|
||||
data-test-subj="video-gif"
|
||||
alt={i18n.WATCH_OVERVIEW_VIDEO}
|
||||
/>
|
||||
<EuiText size="s" grow={false} css={{ marginTop: 20 }}>
|
||||
<h4>
|
||||
<EuiIcon type="cheer" color="success" /> {i18n.ATTACK_DISCOVERY_TOUR_VIDEO_STEP_TITLE}
|
||||
</h4>
|
||||
<p>{i18n.ATTACK_DISCOVERY_TOUR_VIDEO_STEP_DESC}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton color="success" onClick={openVideoInNewTab} fullWidth>
|
||||
{i18n.WATCH_OVERVIEW_VIDEO}
|
||||
</EuiButton>
|
||||
</EuiToast>
|
||||
</div>
|
||||
</EuiPortal>
|
||||
);
|
||||
};
|
||||
|
||||
export const VideoToast = React.memo(VideoComponent);
|
|
@ -137,7 +137,7 @@ const usePanelBottomOffset = (): string | undefined => {
|
|||
* Main security navigation component.
|
||||
* It takes the links to render from the generic application `links` configs.
|
||||
*/
|
||||
export const SecuritySideNav: React.FC<{ onMount?: () => void }> = ({ onMount }) => {
|
||||
export const SecuritySideNav: React.FC = () => {
|
||||
const items = useSolutionSideNavItems();
|
||||
const selectedId = useSelectedId();
|
||||
const panelTopOffset = usePanelTopOffset();
|
||||
|
@ -151,7 +151,6 @@ export const SecuritySideNav: React.FC<{ onMount?: () => void }> = ({ onMount })
|
|||
<SolutionSideNav
|
||||
items={items}
|
||||
categories={CATEGORIES}
|
||||
onMount={onMount}
|
||||
selectedId={selectedId}
|
||||
panelTopOffset={panelTopOffset}
|
||||
panelBottomOffset={panelBottomOffset}
|
||||
|
|
|
@ -23,9 +23,7 @@ const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mai
|
|||
defaultMessage: 'Security',
|
||||
});
|
||||
|
||||
export const useSecuritySolutionNavigation = (
|
||||
onMount: () => void
|
||||
): KibanaPageTemplateProps['solutionNav'] => {
|
||||
export const useSecuritySolutionNavigation = (): KibanaPageTemplateProps['solutionNav'] => {
|
||||
const { chrome } = useKibana().services;
|
||||
const chromeStyle$ = useMemo(() => chrome.getChromeStyle$(), [chrome]);
|
||||
const chromeStyle = useObservable(chromeStyle$, 'classic');
|
||||
|
@ -41,7 +39,7 @@ export const useSecuritySolutionNavigation = (
|
|||
canBeCollapsed: true,
|
||||
name: translatedNavTitle,
|
||||
icon: 'logoSecurity',
|
||||
children: <SecuritySideNav onMount={onMount} />,
|
||||
children: <SecuritySideNav />,
|
||||
closeFlyoutButtonPosition: 'inside',
|
||||
};
|
||||
};
|
||||
|
|
|
@ -35336,13 +35336,6 @@
|
|||
"xpack.securitySolution.attackDiscovery.summaryCount.alertsLabel": "{alertsCount} {alertsCount, plural, =1 {alerte} other {alertes}}",
|
||||
"xpack.securitySolution.attackDiscovery.summaryCount.discoveriesLabel": "{attackDiscoveriesCount} {attackDiscoveriesCount, plural, =1 {découverte} other {découvertes}}",
|
||||
"xpack.securitySolution.attackDiscovery.summaryCount.lastGeneratedLabel": "Généré",
|
||||
"xpack.securitySolution.attackDiscovery.tour.exit": "Fermer",
|
||||
"xpack.securitySolution.attackDiscovery.tour.navStep.desc": "Tirez parti de l’IA générative pour trouver des relations entre vos alertes et détailler les chaînes d’attaque.",
|
||||
"xpack.securitySolution.attackDiscovery.tour.navStep.title": "Présentation d’Attack Discovery",
|
||||
"xpack.securitySolution.attackDiscovery.tour.tryIt": "Essayer",
|
||||
"xpack.securitySolution.attackDiscovery.tour.video": "Regardez la vidéo de présentation",
|
||||
"xpack.securitySolution.attackDiscovery.tour.videoStep.desc": "Plongez dans les découvertes d'attaques axées sur les données et rationalisez votre flux de travail grâce à notre technologie d'IA intuitive, conçue pour accroître instantanément votre productivité.",
|
||||
"xpack.securitySolution.attackDiscovery.tour.videoStep.title": "Démarrez la découverte des attaques",
|
||||
"xpack.securitySolution.auditd.abortedAuditStartupDescription": "démarrage de l'audit abandonné",
|
||||
"xpack.securitySolution.auditd.accessErrorDescription": "erreur d'accès",
|
||||
"xpack.securitySolution.auditd.accessPermissionDescription": "autorisation d'accès",
|
||||
|
|
|
@ -35081,13 +35081,6 @@
|
|||
"xpack.securitySolution.attackDiscovery.summaryCount.alertsLabel": "{alertsCount} {alertsCount, plural, other {件のアラート}}",
|
||||
"xpack.securitySolution.attackDiscovery.summaryCount.discoveriesLabel": "{attackDiscoveriesCount} {attackDiscoveriesCount, plural, other {件の検出}}",
|
||||
"xpack.securitySolution.attackDiscovery.summaryCount.lastGeneratedLabel": "生成済み",
|
||||
"xpack.securitySolution.attackDiscovery.tour.exit": "閉じる",
|
||||
"xpack.securitySolution.attackDiscovery.tour.navStep.desc": "生成AIを活用して、アラート全体の関係を特定し、攻撃チェーンを解析します。",
|
||||
"xpack.securitySolution.attackDiscovery.tour.navStep.title": "Attack Discoveryの概要",
|
||||
"xpack.securitySolution.attackDiscovery.tour.tryIt": "お試しください",
|
||||
"xpack.securitySolution.attackDiscovery.tour.video": "概要動画を視聴",
|
||||
"xpack.securitySolution.attackDiscovery.tour.videoStep.desc": "データ主導のAttack Discoveryを導入し、生産性を即時に高めるために設計されたElasticの直感的なAI技術でワークフローを合理化しましょう。",
|
||||
"xpack.securitySolution.attackDiscovery.tour.videoStep.title": "攻撃の検出を開始",
|
||||
"xpack.securitySolution.auditd.abortedAuditStartupDescription": "中断された監査のスタートアップ",
|
||||
"xpack.securitySolution.auditd.accessErrorDescription": "アクセスエラー",
|
||||
"xpack.securitySolution.auditd.accessPermissionDescription": "アクセス権限",
|
||||
|
|
|
@ -35124,13 +35124,6 @@
|
|||
"xpack.securitySolution.attackDiscovery.summaryCount.alertsLabel": "{alertsCount} 个{alertsCount, plural, other {告警}}",
|
||||
"xpack.securitySolution.attackDiscovery.summaryCount.discoveriesLabel": "{attackDiscoveriesCount} 个{attackDiscoveriesCount, plural, other {发现}}",
|
||||
"xpack.securitySolution.attackDiscovery.summaryCount.lastGeneratedLabel": "已生成",
|
||||
"xpack.securitySolution.attackDiscovery.tour.exit": "关闭",
|
||||
"xpack.securitySolution.attackDiscovery.tour.navStep.desc": "利用生成式 AI 找出您的告警之间的关系并描述攻击链。",
|
||||
"xpack.securitySolution.attackDiscovery.tour.navStep.title": "Attack Discovery 简介",
|
||||
"xpack.securitySolution.attackDiscovery.tour.tryIt": "试用",
|
||||
"xpack.securitySolution.attackDiscovery.tour.video": "观看概述视频",
|
||||
"xpack.securitySolution.attackDiscovery.tour.videoStep.desc": "深入了解数据驱动式 Attack Discovery,并利用旨在即时提高生产力的直观式 AI 技术精简您的工作流。",
|
||||
"xpack.securitySolution.attackDiscovery.tour.videoStep.title": "开始发现攻击",
|
||||
"xpack.securitySolution.auditd.abortedAuditStartupDescription": "已中止审计启动",
|
||||
"xpack.securitySolution.auditd.accessErrorDescription": "访问错误",
|
||||
"xpack.securitySolution.auditd.accessPermissionDescription": "访问权限",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue